I am using the basic authentication with "sendWith...
# ktor
d
I am using the basic authentication with "sendWithoutRequest" on a Multiplatform client project. On Desktop/JVM, it works perfectly, but on Web/JS the Chrome console gives me this error:
Failed to load resource: the server responded with a status of 401 ()
Access to fetch at '<https://mydomain.com/endpoint>' from origin '<http://localhost:8080>' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
This is the Ktor code:
Copy code
install(Auth) {
    basic {
        credentials {
            BasicAuthCredentials(username = authSettings.username, password = authSettings.password)
        }
        realm = "cms"
        sendWithoutRequest { request ->
            request.url.host == "0.0.0.0"
        }
    }
}
Are there issues on Ktor/JS? The endpoint is a Golang webservice, with this code:
Copy code
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, WWW-Authenticate")
if !authorizedOK(r) {
    w.Header().Set("WWW-Authenticate", `Basic realm="cms", charset="UTF-8"`)
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
    return
}
w.Header().Set("Content-Type", "application/json")
resp := myproject.GetCMSResponse(r)
json.NewEncoder(w).Encode(resp)
e
d
I added "OPTIONS" among methods and "Origin" among headers, but I still get the same error
Copy code
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, WWW-Authenticate, Origin")
if !authorizedOK(r) {
    w.Header().Set("WWW-Authenticate", `Basic realm="cms", charset="UTF-8"`)
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
    return
}
w.Header().Set("Content-Type", "application/json")
resp := myproject.GetCMSResponse(r)
json.NewEncoder(w).Encode(resp)
I logged the requests on the Go webservice. === These are the requests by the DESKTOP/JVM app: REQUEST 1:
Copy code
URI: <https://mydomain.com/endpoint>
METHOD: POST
HEADERS:
Accept: application/json
Accept-Charset: UTF-8
Connection: Keep-Alive
Content-Length: 0
User-Agent: Ktor client
RESPONSE by server: NOT authorized REQUEST 2:
Copy code
URI: <https://mydomain.com/endpoint>
METHOD: POST
HEADERS:
Accept: application/json
Accept-Charset: UTF-8
Authorization: Basic ZGFuaAbsZTp1ZmlttWM5Mw==
Connection: Keep-Alive
Content-Length: 0
User-Agent: Ktor client
RESPONSE by server: AUTHORIZED === These are the requests by the WEB/JS app: REQUEST 1:
Copy code
URI: <https://mydomain.com/endpoint>
METHOD: POST
HEADERS:
Accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,it-IT;q=0.8,it;q=0.7,en-US;q=0.6,th;q=0.5
Content-Length: 0
Origin: <http://localhost:8080>
Referer: <http://localhost:8080/>
Sec-Ch-Ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
RESPONSE by server: NOT authorized REQUEST 2:
Copy code
URI: <https://mydomain.com/endpoint>
METHOD: OPTIONS
HEADERS:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,it-IT;q=0.8,it;q=0.7,en-US;q=0.6,th;q=0.5
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: POST
Origin: <http://localhost:8080>
Referer: <http://localhost:8080/>
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
RESPONSE by server: NOT authorized
So, the Web/JS client is not sending the "Authorization" header
it looks like there are some bug in the Web/JS client implementation of "sendWithoutRequest"
e
the question is not whether OPTIONS is in that list, it is that the backend needs to respond successfully to an OPTIONS request
https://fetch.spec.whatwg.org/#cors-protocol-and-credentials explicitly says that credentials are excluded from preflight
d
thanks @ephemient! by giving giving a successful response to OPTIONS, it then includes the "Authorization" header to the next request! This is my new server-side code:
Copy code
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == http.MethodOptions {
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Authorization, Origin")
    return
}
if !authorizedOK(r) {
    w.Header().Set("WWW-Authenticate", `Basic realm="cms", charset="UTF-8"`)
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
    return
}
w.Header().Set("Content-Type", "application/json")
resp := myproject.GetCMSResponse(r)
json.NewEncoder(w).Encode(resp)
However, I just noticed (in both Desktop/JVM and Web/JS !!!) that any following request it doesn't include the "Authorization" header, despite "sendWithoutRequest" is set in the configuration, so the first request always fails, and then (after the server sends the "WWW-Authenticate" header) the next request succeeds, as it includes the "Authorization" header. Isn't Ktor Client's "sendWithoutRequest" supposed to send the Authorization header to each request?
a
Yes, if the condition is met.
d
Can you please explain what I am doing wrong here. Why is the condition not met in my case?
a
@Daniele B your backend doesn't support CORS, just enable that and you'll be good to go
d
@andylamax CORS isn't something arcane. It consists in just returning specific headers in the response, which is what I am doing. However the fact that even on Desktop, the first client request doesn't include the "Authorization" header, makes me think that the problem isn't in the backend.
The Ktor Client documentation says: "to enable sending credentials in the initial request without waiting for a
401
(Unauthorized) response with the
WWW-Authenticate
header, you need to call the
sendWithoutRequest
function returning boolean and check the request parameters"
a
However the fact that even on Desktop, the first client request doesn't include the "Authorization" header, makes me think that the problem isn't in the backend.
Can you please file an issue with a code snippet for reproduction attached?
thanks @Aleksei Tirman [JB] mystery solved!
Copy code
sendWithoutRequest { request -> request.url.host == "0.0.0.0" }
didn't include localhost:8080 with this it works!
Copy code
sendWithoutRequest { request -> true }
Actually the server wasn't "localhost:8080", but was remote on the port 80. So, it looks like "0.0.0.0" doesn't mean anything. It's supposed to be replaced by the specific host IP address. Probably the documentation should be made clearer, otherwise "0.0.0.0" could be misleadingly interpreted as any host.