Ktor 3.0.0-rc-1 Help

Digest authentication

The Digest authentication scheme is a part of the HTTP framework used for access control and authentication. In this scheme, a hash function is applied to a username and password before sending them over the network.

Ktor allows you to use digest authentication for logging in users and protecting specific routes. You can get general information about authentication in Ktor in the Authentication and authorization section.

Add dependencies

To enable digest authentication, you need to include the ktor-server-auth artifact in the build script:

implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation "io.ktor:ktor-server-auth:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-auth-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Digest authentication flow

The digest authentication flow looks as follows:

  1. A client makes a request without the Authorization header to a specific route in a server application.

  2. A server responds to a client with a 401 (Unauthorized) response status and uses a WWW-Authenticate response header to provide information that the digest authentication scheme is used to protect a route. A typical WWW-Authenticate header looks like this:

    WWW-Authenticate: Digest realm="Access to the '/' path", nonce="e4549c0548886bc2", algorithm="MD5"

    In Ktor, you can specify the realm and the way of generating a nonce value when configuring the digest authentication provider.

  3. Usually a client displays a login dialog where a user can enter credentials. Then, a client makes a request with the following Authorization header:

    Authorization: Digest username="jetbrains", realm="Access to the '/' path", nonce="e4549c0548886bc2", uri="/", algorithm=MD5, response="6299988bb4f05c0d8ad44295873858cf"

    The response value is generated in the following way:

    a. HA1 = MD5(username:realm:password)

    b. HA2 = MD5(method:digestURI)

    c. response = MD5(HA1:nonce:HA2)

  4. A server validates credentials sent by a client and responds with the requested content.

Install digest authentication

To install the digest authentication provider, call the digest function inside the install block:

import io.ktor.server.application.* import io.ktor.server.auth.* // ... install(Authentication) { digest { // Configure digest authentication } }

You can optionally specify a provider name that can be used to authenticate a specified route.

Configure digest authentication

To get a general idea of how to configure different authentication providers in Ktor, see Configure Authentication. In this section, we'll see on configuration specifics of the digest authentication provider.

Step 1: Provide a user table with digests

The digest authentication provider validates user credentials using the HA1 part of a digest message. So, you can provide a user table that contains usernames and corresponding HA1 hashes. In the example below, the getMd5Digest function is used to generate HA1 hashes:

fun getMd5Digest(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray(UTF_8)) val myRealm = "Access to the '/' path" val userTable: Map<String, ByteArray> = mapOf( "jetbrains" to getMd5Digest("jetbrains:$myRealm:foobar"), "admin" to getMd5Digest("admin:$myRealm:password") )

Step 2: Configure a digest provider

The digest authentication provider exposes its settings via the DigestAuthenticationProvider.Config class. In the example below, the following settings are specified:

  • The realm property sets the realm to be passed in the WWW-Authenticate header.

  • The digestProvider function fetches the HA1 part of digest for a specified username.

  • (Optional) The validate function allows you to map the credentials to a custom principal.

fun Application.main() { install(Authentication) { digest("auth-digest") { realm = myRealm digestProvider { userName, realm -> userTable[userName] } validate { credentials -> if (credentials.userName.isNotEmpty()) { CustomPrincipal(credentials.userName, credentials.realm) } else { null } } } } } data class CustomPrincipal(val userName: String, val realm: String) : Principal

You can also use the nonceManager property to specify how to generate nonce values.

Step 3: Protect specific resources

After configuring the digest provider, you can protect specific resources in our application using the authenticate function. In the case of successful authentication, you can retrieve an authenticated Principal inside a route handler using the call.principal function and get a name of an authenticated user.

routing { authenticate("auth-digest") { get("/") { call.respondText("Hello, ${call.principal<CustomPrincipal>()?.userName}!") } } }
Last modified: 02 April 2024