I am trying to use ktor client in Kotlin/MPP (Multiplatform) project and on JVM target feature basic authentication does not seem to have an effect.
import io.ktor.client.HttpClient
import io.ktor.client.features.ResponseException
import io.ktor.client.features.auth.Auth
import io.ktor.client.features.auth.providers.basic
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.DEFAULT
import io.ktor.client.features.logging.LogLevel
import io.ktor.client.features.logging.Logger
import io.ktor.client.features.logging.Logging
import io.ktor.client.request.get
import io.ktor.client.request.header
import kotlinx.coroutines.runBlocking
import java.util.*
fun main() = runBlocking {
val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(Auth) {
basic {
username = "user"
password = "pass"
}
}
}
val url = "https://en.wikipedia.org/wiki/Main_Page"
val failing = try {
client.get<String>(url)
} catch (e: ResponseException) {
"failed"
}
val succeeding = try {
client.get<String>(url) {
header("Authorization", "Basic ${Base64.getEncoder().encodeToString("user:pass".toByteArray())}")
}
} catch (e: ResponseException) {
"failed"
}
}
From the logger output, you can see that client does not send Authorization
header but I experience no problems when I provide such header manually:
First request (failing example:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Second request (succeeding example:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Ktor Artifacts version 1.3.1:
Please add sendWithoutRequest = true
1.x https://api.ktor.io/1.3.1/io.ktor.client.features.auth.providers/-basic-auth-config/send-without-request.html
install(Auth) {
basic {
sendWithoutRequest = true
username = "user"
password = "pass"
}
}
2.x https://ktor.io/docs/basic-client.html#configure
install(Auth) {
basic {
sendWithoutRequest { true }
credentials {
BasicAuthCredentials(
username = "user",
password = "pass",
)
}
}
}
Result:
sending with sendWithoutRequest set to true
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Explanation:
By default, Ktor will wait for the server to respond with 401, Unauthorized, and only then send the authentication header. In your example, wiki never responds with a 401, as it is not a protected resource. Therefore, adding sendWithoutRequest is required. If you tried with some url that does respond with a 401, you would see that Ktor will then send a second request (after receiving 401) with the authentication header. You can try with this url to see - https://api.sumologic.com/api/v1/collectors.
This is the logging when done against that protected api with sendWithoutRequest turned off, your original input. As you can see, there are now 2 requests made, the first without the authorization header, and then the second one, with the authorization header, after the server has responded with a 401.
sending with sendWithoutRequest set to false and hitting a protected resource
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
Note: I just saw a comment by Andylamax that a new version "fixes" it. Perhaps, I don't know as I haven' tried with that new version. But I would like to add that this is not something unique to Ktor, and at least in this respect is not a bug (but maybe they changed their minds? Again, I don't know). In fact, it is my experience with C# that led me to suspect what's going in here and find the answer. The WebRequest in C# behaves the same way, you need to set PreAuthenticate to true to send the credentials immediately. See here https://learn.microsoft.com/en-us/dotnet/api/system.net.webrequest.preauthenticate?view=netcore-3.1.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With