I would like to use this method to authenticate client/server communication in my app, but can't figure out why getting error. Code in super simple and come from original ktor example.
Server:
val myRealm = "Access to the '/' path"
val userTable: Map<String, ByteArray> = mapOf(
"jetbrains" to getMd5Digest("jetbrains:$myRealm:foobar"),
"admin" to getMd5Digest("admin:$myRealm:password")
fun Application.configureSecurity() {
install(Authentication) {
digest("myDigestAuth") {
digestProvider { userName, realm ->
userTable[userName]
}
}
}
routing {
authenticate("myDigestAuth") {
get("/protected/route/digest") {
val principal = call.principal<UserIdPrincipal>()!!
call.respondText("Hello ${principal.name}")
}
}
}
}
Client:
val client = HttpClient(CIO) {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(Auth) {
digest {
credentials {
DigestAuthCredentials(username = "jetbrains", password = "foobar")
}
realm = myRealm
}
}
}
val response: HttpResponse = client.get("http://0.0.0.0:8080/protected/route/digest")
Here the log of the communication:
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - REQUEST: http://0.0.0.0:8080/protected/route/digest
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - COMMON HEADERS
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept: */*
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - CONTENT HEADERS
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Content-Length: 0
[ktor-jetty-8080-1] INFO ktor.application - 401 Unauthorized: GET - /protected/route/digest
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - RESPONSE: 401 Unauthorized
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - FROM: http://0.0.0.0:8080/protected/route/digest
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - COMMON HEADERS
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Content-Length: 0
[DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> WWW-Authenticate: Digest realm="Ktor Server", nonce="ce825e23d3275f40", algorithm="MD5"
And this is why client client immediately fail with:
Exception in thread "main" io.ktor.client.features.ClientRequestException: Client request(http://0.0.0.0:8080/protected/route/digest) invalid: 401 Unauthorized. Text: ""
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:47)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:54)
at io.ktor.client.features.HttpCallValidator.access$validateResponse(HttpCallValidator.kt:33)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:133)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:96)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:191)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:147)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:15)
at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:93)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:191)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:147)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:15)
at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:93)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Don't know if it right and/or related with the error.... but I was expecting in the header from the server to get the "realm" as per set in the code "Access to the '/' path" and not "Ktor server"
CodePudding user response:
When setting up the server, you're missing the realm:
install(Authentication) {
digest("myDigestAuth") {
realm = myRealm // This part is missing from your code
digestProvider { userName, realm ->
userTable[userName]
}
}
}
Reference: https://ktor.io/docs/digest.html#configure-provider
Without this, the server uses the default realm ("Ktor server"), and the client doesn't have credentials for that realm.