From 5832c94b86d36e99f576c3f97c64ed9eaafe680f Mon Sep 17 00:00:00 2001 From: vnikolova Date: Wed, 11 Mar 2026 17:45:48 +0100 Subject: [PATCH 1/4] fix file formatting, language, and grammar in client-bearer-auth.md --- topics/client-bearer-auth.md | 292 +++++++++++++++++++---------------- 1 file changed, 157 insertions(+), 135 deletions(-) diff --git a/topics/client-bearer-auth.md b/topics/client-bearer-auth.md index 68d2dfbdb..2ca7ceb21 100644 --- a/topics/client-bearer-auth.md +++ b/topics/client-bearer-auth.md @@ -1,6 +1,6 @@ [//]: # (title: Bearer authentication in Ktor Client) - +

@@ -10,190 +10,214 @@ +Bearer authentication uses security tokens called _bearer tokens_. These tokens are commonly used in OAuth 2.0 flows to +authorize users through external providers, such as Google, Facebook, and X. -Bearer authentication involves security tokens called bearer tokens. As an example, these tokens can be used as a part -of OAuth flow to authorize users of your application by using external providers, such as Google, Facebook, Twitter, and -so on. You can learn how the OAuth flow might look from the [OAuth authorization flow](server-oauth.md#flow) section for -a Ktor server. +You can learn more about the OAuth process in the [OAuth authorization flow section of +the Ktor server documentation](server-oauth.md#flow). > On the server, Ktor provides the [Authentication](server-bearer-auth.md) plugin for handling bearer authentication. ## Configure bearer authentication {id="configure"} -A Ktor client allows you to configure a token to be sent in the `Authorization` header using the `Bearer` scheme. You can also specify the logic for refreshing a token if the old one is invalid. To configure the `bearer` provider, follow the steps below: - -1. Call the `bearer` function inside the `install` block. - ```kotlin - import io.ktor.client.* - import io.ktor.client.engine.cio.* - import io.ktor.client.plugins.auth.* - //... - val client = HttpClient(CIO) { - install(Auth) { - bearer { - // Configure bearer authentication - } - } - } - ``` - -2. Configure how to obtain the initial access and refresh tokens using the `loadTokens` callback. This callback is intended to load cached tokens from a local storage and return them as the `BearerTokens` instance. +A Ktor client allows you to send a token in the `Authorization` header using the `Bearer` scheme. You +can also define logic that refreshes tokens when they expire. - ```kotlin +To configure bearer authentication, install the `Auth` plugin and configure the `bearer` provider: + +```kotlin +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.auth.* +//... +val client = HttpClient(CIO) { install(Auth) { - bearer { - loadTokens { - // Load tokens from a local storage and return them as the 'BearerTokens' instance - BearerTokens("abc123", "xyz111") - } + bearer { + // Configure bearer authentication + } + } +} +``` + +### Load tokens + +Use the `loadTokens` callback to provide the initial access and refresh tokens. Typically, this callback loads cached +tokens from local storage and returns them as a `BearerTokens` instance. + +```kotlin +install(Auth) { + bearer { + loadTokens { + // Load tokens from a local storage and return them as the 'BearerTokens' instance + BearerTokens("abc123", "xyz111") } } - ``` - - The `abc123` access token is sent with each [request](client-requests.md) in the `Authorization` header using the `Bearer` scheme: - ```HTTP - GET http://localhost:8080/ - Authorization: Bearer abc123 - ``` - -3. Specify how to obtain a new token if the old one is invalid using `refreshTokens`. +} +``` - ```kotlin - install(Auth) { - bearer { - // Load tokens ... - refreshTokens { // this: RefreshTokensParams - // Refresh tokens and return them as the 'BearerTokens' instance - BearerTokens("def456", "xyz111") - } +In this example, the client sends the `abc123` access token in the `Authorization` header: + +```HTTP +GET http://localhost:8080/ +Authorization: Bearer abc123 +``` + +### Refresh tokens + +Use the `refreshTokens` callback to define how the client obtains new tokens when the current access token becomes +invalid: + +```kotlin +install(Auth) { + bearer { + // Load tokens ... + refreshTokens { // this: RefreshTokensParams + // Refresh tokens and return them as the 'BearerTokens' instance + BearerTokens("def456", "xyz111") } } - ``` - - This callback works as follows: +} +``` - a. The client makes a request to a protected resource using an invalid access token and gets a `401` (Unauthorized) response. - > If [several providers](client-auth.md#realm) are installed, a response should have the `WWW-Authenticate` header. +The refresh process works as follows: - b. The client calls `refreshTokens` automatically to obtain new tokens. +1. The client makes a request to a protected resource using an invalid access token. +2. The resource server returns a `401 Unauthorized` response. + > If [several providers](client-auth.md#realm) are installed, a response should have the `WWW-Authenticate` header. +3. The client automatically invokes `refreshTokens` to obtain new tokens. +4. The client retries the request to a protected resource using the new token. - c. The client makes one more request to a protected resource automatically using a new token this time. +### Send credentials without waiting for 401 -4. (Optional) Specify a condition for sending credentials without waiting for the `401` (Unauthorized) response. For example, you can check whether a request is made to a specified host. +By default, the client sends credentials only after receiving a `401 Unauthorized` response. - ```kotlin - install(Auth) { - bearer { - // Load and refresh tokens ... - sendWithoutRequest { request -> - request.url.host == "www.googleapis.com" - } +You can override this behavior using the `sendWithoutRequest` callback function. This callback determines whether the +client should attach credentials before sending the request. + +For example, the following configuration always sends the token when accessing Google APIs: + +```kotlin +install(Auth) { + bearer { + // Load and refresh tokens ... + sendWithoutRequest { request -> + request.url.host == "www.googleapis.com" } } - ``` +} +``` + +### Cache tokens + +Use the `cacheTokens` property to control whether bearer tokens are cached between requests. -5. (Optional) Use the `cacheTokens` option to control whether bearer tokens are cached between requests. Disabling - caching forces the client to reload tokens for every request, which can be useful when tokens change frequently: +If caching is disabled, the client calls the `loadTokens {}` function for every request: - ```kotlin - install(Auth) { - bearer { - cacheTokens = false // Reloads tokens for every request - loadTokens { - loadDynamicTokens() - } +```kotlin +install(Auth) { + bearer { + cacheTokens = false // Reloads tokens for every request + loadTokens { + loadDynamicTokens() } } - ``` +} +``` + +Disabling caching can be useful when tokens change frequently. - > For details on clearing cached credentials programmatically, see the general [Token caching and cache control](client-auth.md#token-caching) - > section. +> For details on clearing cached credentials programmatically, see the general [Token caching and cache control](client-auth.md#token-caching) +> documentation. +> +{style="tip"} ## Example: Using Bearer authentication to access Google API {id="example-oauth-google"} -Let's take a look at how to use bearer authentication to access Google APIs, which use the [OAuth 2.0 protocol](https://developers.google.com/identity/protocols/oauth2) for authentication and authorization. We'll investigate the [client-auth-oauth-google](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/client-auth-oauth-google) console application that gets Google's profile information. +This example demonstrates how to use bearer authentication with Google APIs, which use the [OAuth 2.0 protocol](https://developers.google.com/identity/protocols/oauth2) +for authentication and authorization. + +The example application [client-auth-oauth-google](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/client-auth-oauth-google) retrieves the user's Google profile information. ### Obtain client credentials {id="google-client-credentials"} -To access Google APIs, you first need OAuth client credentials: + +To access Google APIs, you first need to obtain OAuth client credentials: + 1. Create or sign in to a Google account. -2. Open the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) and create an `OAuth client ID` with the `Android` application type. This client -ID will be used to obtain an [authorization grant](#step1). +2. Open the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) +3. Create an `OAuth client ID` with the `Android` application type. You will use this client +ID to obtain an [authorization grant](#step1). ### OAuth authorization flow {id="oauth-flow"} -The OAuth authorization flow looks as follows: - -```Console -(1) --> [[[Authorization request|#step1]]] Resource owner -(2) <-- [[[Authorization grant (code)|#step2]]] Resource owner -(3) --> [[[Authorization grant (code)|#step3]]] Authorization server -(4) <-- [[[Access and refresh tokens|#step4]]] Authorization server -(5) --> [[[Request with valid token|#step5]]] Resource server -(6) <-- [[[Protected resource|#step6]]] Resource server -⌛⌛⌛ Token expired -(7) --> [[[Request with expired token|#step7]]] Resource server -(8) <-- [[[401 Unauthorized response|#step8]]] Resource server -(9) --> [[[Authorization grant (refresh token)|#step9]]] Authorization server -(10) <-- [[[Access and refresh tokens|#step10]]] Authorization server -(11) --> [[[Request with new token|#step11]]] Resource server -(12) <-- [[[Protected resource|#step12]]] Resource server -``` -{disable-links="false"} +The OAuth authorization flow consists of the following steps: + +1. The client sends an [authorization request](#step1) to the resource owner. +2. The resource owner [returns an authorization code](#step2). +3. The client [sends the authorization code](#step3) to the authorization server. +4. The authorization server [returns access and refresh tokens](#step4). +5. The client [sends a request to the resource server using the access token](#step5). +6. The resource server [returns the protected resource](#step6). +7. After the access token expires, the client [sends a request with the expired token](#step7). +8. The resource server [responds with 401 Unauthorized](#step8). +9. The client [sends the refresh token](#step9) to the authorization server. +10. The authorization server [returns new access and refresh tokens](#step10). +11. The client [sends a new request to the resource server using the new access token](#step11). +12. The resource server [returns the protected resource](#step12). -The next sections explain how each step is implemented and how the `Bearer` authentication provider assists in -accessing the API. +The following sections explain how the Ktor client implements each step. -### (1) -> Authorization request {id="step1"} +#### Authorization request {id="step1"} -The first step is to construct the authorization URL used to request the necessary permissions. This is done by appending +First, construct the authorization URL used to request the necessary permissions. This is done by appending the required query parameters: ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="23-31"} -- `client_id`: the client ID [obtained earlier](#google-client-credentials) used to access the Google APIs. -- `scope`: the scopes of resources required for a Ktor application. In this case, the application requests information about a user's profile. -- `response_type`: a grant type used to get an access token. In this case, it is set to `"code"` to obtain an authorization code. +- `client_id`: the [OAuth client ID](#google-client-credentials) used to access the Google APIs. +- `scope`: the permissions requested by the application. In this case, it is information about a user's profile. +- `response_type`: a grant type used to get an access token. Set to `"code"` to obtain an authorization code. - `redirect_uri`: the `http://127.0.0.1:8080` value indicates that the _Loopback IP address_ flow is used to get the authorization code. > To receive the authorization code using this URL, your application must be listening on the local web server. > For example, you can use a [Ktor server](server-create-and-configure.topic) to get the authorization code as a query parameter. - `access_type`: set to `offline` so that the application can refresh access tokens when the user is not present at the browser. -### (2) <- Authorization grant (code) {id="step2"} +#### Authorization grant (code) {id="step2"} -Copy the authorization code from the browser, paste it in a console, and save it in a variable: +After granting access, the browser returns an authorization code. Copy the code and store it in a variable: ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="32"} -### (3) -> Authorization grant (code) {id="step3"} +#### Exchange authorization code for tokens {id="step3"} Next, exchange the authorization code for tokens. To do this, create a client and install the -[ContentNegotiation](client-serialization.md) plugin with the `json` serializer. This serializer is required to deserialize tokens received -from the Google OAuth token endpoint. +[`ContentNegotiation`](client-serialization.md) plugin with the JSON serializer: ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="38-41,65"} -Using the created client, you can securely pass the authorization code and other necessary options to the token -endpoint as [form parameters](client-requests.md#form_parameters): +This serializer is required to deserialize tokens received from the Google OAuth token endpoint. + +Using the created client, pass the authorization code and other necessary options to the token endpoint as +[form parameters](client-requests.md#form_parameters): ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="68-77"} -As a result, the token endpoint sends tokens in a JSON object, which is deserialized to a `TokenInfo` class instance -using the installed `json` serializer. The `TokenInfo` class looks as follows: +The token endpoint returns a JSON response that the client deserializes into a `TokenInfo` instance. The `TokenInfo` +class looks as follows: ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/models/TokenInfo.kt" include-lines="3-13"} -### (4) <- Access and refresh tokens {id="step4"} +#### Store tokens {id="step4"} Once the tokens are received, store them so they can be supplied to the `loadTokens` and `refreshTokens` callbacks. In this example, the storage is a mutable list of `BearerTokens`: @@ -202,15 +226,17 @@ this example, the storage is a mutable list of `BearerTokens`: ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="35-36,78"} -> Note that `bearerTokenStorage` should be created before [initializing the client](#step3) since it will be used inside the client configuration. +> Create the token storage before [initializing the client](#step3) because it will be used +> inside the client configuration. +> +{style="note"} - -### (5) -> Request with valid token {id="step5"} +#### Send a request with a valid token {id="step5"} Now that valid tokens are available, the client can make a request to the protected Google API and retrieve user information. -Before doing that, you need to adjust the client [configuration](#step3): +Before doing that, configure the client to use bearer authentication: ```kotlin ``` @@ -218,13 +244,9 @@ Before doing that, you need to adjust the client [configuration](#step3): The following settings are specified: -- The already installed [ContentNegotiation](client-serialization.md) plugin with the `json` serializer is required to deserialize user -information received from a resource server in a JSON format. - -- The [Auth](client-auth.md) plugin with the `bearer` provider is configured as follows: - * The `loadTokens` callback loads tokens from the [storage](#step4). - * The `sendWithoutRequest` callback sends the access token without waiting for the `401 Unauthorized` response when - accessing Google's protected API. +* The `loadTokens` callback retrieves tokens from [storage](#step4). +* The `sendWithoutRequest` callback sends the access token without waiting for the `401 Unauthorized` response when + calling the Google API. With this client, you can now make a request to the protected resource: @@ -233,7 +255,7 @@ With this client, you can now make a request to the protected resource: {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="81-96"} -### (6) <- Protected resource {id="step6"} +#### Access the protected resource {id="step6"} The resource server returns information about a user in a JSON format. You can deserialize the response into the `UserInfo` class instance and display a personal greeting: @@ -250,21 +272,21 @@ The `UserInfo` class looks as follows: {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/models/UserInfo.kt" include-lines="3-13"} -### (7) -> Request with expired token {id="step7"} +#### Request with expired token {id="step7"} At some point, the client repeats the request from [Step 5](#step5), but with an expired access token. -### (8) <- 401 Unauthorized response {id="step8"} +#### 401 Unauthorized response {id="step8"} When the token is no longer valid, the resource server returns a `401 Unauthorized` response. The client then invokes the `refreshTokens` callback, which is responsible for obtaining new tokens. > The `401` response returns JSON data with error details. This needs to be [handled when receiving a response](#step12). -### (9) -> Authorization grant (refresh token) {id="step9"} +#### Refresh the access token {id="step9"} -To obtain a new access token, you need to configure `refreshTokens` to make another request to the token endpoint. This -time, the `refresh_token` grant type is used instead of `authorization_code`: +To obtain a new access token, configure `refreshTokens` to make another request to the token endpoint. This +time, use the `refresh_token` grant type instead of `authorization_code`: ```kotlin ``` @@ -277,9 +299,9 @@ The `refreshTokens` callback uses `RefreshTokensParams` as a receiver and allows > The `markAsRefreshTokenRequest` function exposed by `HttpRequestBuilder` enables special handling of requests used > to obtain a refresh token. -### (10) <- Access and refresh tokens {id="step10"} +#### Save refreshed tokens {id="step10"} -After receiving new tokens, they need to be saved in the [token storage](#step4). With this, the `refreshTokens` callback +After receiving new tokens, save them in the [token storage](#step4). With this, the `refreshTokens` callback looks as follows: ```kotlin @@ -287,16 +309,16 @@ looks as follows: {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="48-59"} -### (11) -> Request with new token {id="step11"} +#### Request with new token {id="step11"} With the refreshed access token stored, the next request to the protected resource should succeed: ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="85"} -### (12) <-- Protected resource {id="step12"} +#### Handle API errors {id="step12"} -Given that the [401 response](#step8) returns JSON data with error details, update the example to read error responses +Given that the [`401` response](#step8) returns JSON data with error details, update the example to read error responses as an `ErrorInfo` object: ```kotlin From 70d461713262ff247c4586037174b935e982bb49 Mon Sep 17 00:00:00 2001 From: vnikolova Date: Fri, 13 Mar 2026 18:02:36 +0100 Subject: [PATCH 2/4] fix client-auth.md file formatting, add provider selection and a single auth provider test --- .../client-auth-oauth-google/build.gradle.kts | 2 + .../src/test/kotlin/ApplicationTest.kt | 60 ++++++++++++++++ topics/client-auth.md | 70 ++++++++++++------- topics/client-bearer-auth.md | 50 +++++++------ 4 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 codeSnippets/snippets/client-auth-oauth-google/src/test/kotlin/ApplicationTest.kt diff --git a/codeSnippets/snippets/client-auth-oauth-google/build.gradle.kts b/codeSnippets/snippets/client-auth-oauth-google/build.gradle.kts index c02af14f9..832481859 100644 --- a/codeSnippets/snippets/client-auth-oauth-google/build.gradle.kts +++ b/codeSnippets/snippets/client-auth-oauth-google/build.gradle.kts @@ -32,4 +32,6 @@ dependencies { implementation("io.ktor:ktor-server-core:$ktor_version") implementation("io.ktor:ktor-server-netty:$ktor_version") implementation("ch.qos.logback:logback-classic:$logback_version") + testImplementation("junit:junit:$junit_version") + testImplementation("io.ktor:ktor-client-mock:$ktor_version") } diff --git a/codeSnippets/snippets/client-auth-oauth-google/src/test/kotlin/ApplicationTest.kt b/codeSnippets/snippets/client-auth-oauth-google/src/test/kotlin/ApplicationTest.kt new file mode 100644 index 000000000..42016c786 --- /dev/null +++ b/codeSnippets/snippets/client-auth-oauth-google/src/test/kotlin/ApplicationTest.kt @@ -0,0 +1,60 @@ +package com.example + +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respond +import io.ktor.client.plugins.auth.Auth +import io.ktor.client.plugins.auth.providers.BearerTokens +import io.ktor.client.plugins.auth.providers.bearer +import io.ktor.client.request.get +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.headersOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test + +class ApplicationTest { + @Test + fun testSingleAuthProviderUsedDespiteDifferentAuthenticateHeader() = runBlocking { + var firstRequest = true + + val engine = MockEngine { request -> + val authHeader = request.headers[HttpHeaders.Authorization] + + if (firstRequest) { + firstRequest = false + respond( + content = "", + status = HttpStatusCode.Unauthorized, + headers = headersOf( + HttpHeaders.WWWAuthenticate, + "Basic realm=\"Test\"" + ) + ) + } else { + assertEquals("Bearer valid", authHeader) + respond( + content = "OK", + status = HttpStatusCode.OK + ) + } + } + + val client = HttpClient(engine) { + install(Auth) { + bearer { + loadTokens { + BearerTokens("invalid", "refresh") + } + + refreshTokens { + BearerTokens("valid", "refresh") + } + } + } + } + val response = client.get("https://test.example") + assertEquals(HttpStatusCode.OK, response.status) + } +} diff --git a/topics/client-auth.md b/topics/client-auth.md index 9b7c06993..071e2e4bf 100644 --- a/topics/client-auth.md +++ b/topics/client-auth.md @@ -20,18 +20,25 @@ Typical usage scenarios include logging in users and gaining access to specific > On the server, Ktor provides the [`Authentication`](server-auth.md) plugin for handling authentication and > authorization. +> +{style="tip"} ## Supported authentication types {id="supported"} -HTTP provides a [general framework](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) for access control and authentication. The Ktor client allows you to use the following HTTP authentication schemes: +HTTP provides a [general framework](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) for access control and authentication. The Ktor client allows you to use the +following HTTP authentication schemes: -* [Basic](client-basic-auth.md) - uses `Base64` encoding to provide a username and password. Generally is not recommended if not used in combination with HTTPS. -* [Digest](client-digest-auth.md) - an authentication method that communicates user credentials in an encrypted form by applying a hash function to the username and password. -* [Bearer](client-bearer-auth.md) - an authentication scheme that involves security tokens called bearer tokens. For example, you can use this scheme as a part of OAuth flow to authorize users of your application by using external providers, such as Google, Facebook, Twitter, and so on. +* [Basic](client-basic-auth.md) - uses `Base64` encoding to provide a username and password. Generally is not recommended if not used + in combination with HTTPS. +* [Digest](client-digest-auth.md) - an authentication method that communicates user credentials in an encrypted form by applying a hash + function to the username and password. +* [Bearer](client-bearer-auth.md) - an authentication scheme that involves security tokens called bearer tokens. For example, you can + use this scheme as a part of OAuth flow to authorize users of your application by using external providers, such as + Google, Facebook, and X. ## Add dependencies {id="add_dependencies"} -To enable authentication, you need to include the `ktor-client-auth` artifact in the build script: +To enable authentication, include the `ktor-client-auth` artifact in the build script: @@ -39,6 +46,7 @@ To enable authentication, you need to include the `ktor-client-auth` artifact in ## Install Auth {id="install_plugin"} + To install the `Auth` plugin, pass it to the `install()` function inside a [client configuration block](client-create-and-configure.md#configure-client): ```kotlin @@ -52,17 +60,16 @@ val client = HttpClient(CIO) { } } ``` -Now you can [configure](#configure_authentication) the required authentication provider. - - ## Configure authentication {id="configure_authentication"} -### Step 1: Choose an authentication provider {id="choose-provider"} +### Choose an authentication provider {id="choose-provider"} To use a specific authentication provider ([`basic`](client-basic-auth.md), [`digest`](client-digest-auth.md), or -[`bearer`](client-bearer-auth.md)), you need to call the corresponding function inside the `install {}` block. For example, -to use the `basic` authentication, call the [`basic {}`](https://api.ktor.io/ktor-client-auth/io.ktor.client.plugins.auth.providers/basic.html) function: +[`bearer`](client-bearer-auth.md)), call the corresponding function inside the `install {}` block. + +For example, to configure basic authentication, use the [`basic {}`](https://api.ktor.io/ktor-client-auth/io.ktor.client.plugins.auth.providers/basic.html) +function: ```kotlin install(Auth) { @@ -71,10 +78,17 @@ install(Auth) { } } ``` + Inside the block, you can configure settings specific to this provider. +> For provider-specific settings, see the corresponding topic: +> * [Basic authentication](client-basic-auth.md) +> * [Digest authentication](client-digest-auth.md) +> * [Bearer authentication](client-bearer-auth.md) +> +{style="tip"} -### Step 2: (Optional) Configure the realm {id="realm"} +### Configure the realm {id="realm"} Optionally, you can configure the realm using the `realm` property: @@ -102,21 +116,25 @@ install(Auth) { } ``` -In this case, the client chooses the necessary provider based on the `WWW-Authenticate` response header, which contains the realm. +In this case, the client chooses the necessary provider based on the `WWW-Authenticate` response header, +which contains the realm. + +## Provider selection +When a server returns `401 Unauthorized`, the client selects an authentication provider based on the `WWW-Authenticate` +response header. This header specifies which authentication schemes the server accepts. -### Step 3: Configure a provider {id="configure-provider"} +If the client has only one authentication provider installed, the `Auth` plugin always attempts that provider when the +server returns `401 Unauthorized`, even if the `WWW-Authenticate` header is missing or specifies a different scheme. -To learn how to configure settings for a specific [provider](#supported), see a corresponding topic: -* [](client-basic-auth.md) -* [](client-digest-auth.md) -* [](client-bearer-auth.md) +If the client has multiple authentication providers installed, the client selects the provider based on the +`WWW-Authenticate` header. ## Token caching and cache control {id="token-caching"} -The Basic and Bearer authentication providers maintain an internal credential or token cache. This cache allows the -client to reuse previously loaded authentication data instead of reloading it for each request, improving performance -while still allowing full control when credentials change. +The [basic](client-basic-auth.md) and [bearer](client-bearer-auth.md) authentication providers maintain an internal +credential or token cache. This cache allows the client to reuse previously loaded authentication data instead of +reloading it for each request, improving performance while still allowing full control when credentials change. ### Accessing authentication providers @@ -137,14 +155,14 @@ These utilities allow you to inspect providers or clear cached tokens programmat ### Clearing cached tokens -To clear cached credentials for a single provider, use the `clearToken()` function: +To clear cached credentials for a single provider, use the `.clearToken()` function: ```kotlin val provider = client.authProvider() provider?.clearToken() ``` -To clear cached tokens across all authentication providers that support cache clearing, use the `clearAuthTokens()` +To clear cached tokens across all authentication providers that support cache clearing, use the `.clearAuthTokens()` function: ```kotlin @@ -153,9 +171,9 @@ client.clearAuthTokens() Clearing cached tokens is typically used in the following scenarios: -- When the user logs out. -- When credentials or tokens stored by your application change. -- When you need to force providers to reload the authentication state on the next request. +* When the user logs out. +* When credentials or tokens stored by your application change. +* When you need to force providers to reload the authentication state on the next request. Here's an example for clearing cached tokens when the user logs out: diff --git a/topics/client-bearer-auth.md b/topics/client-bearer-auth.md index 2ca7ceb21..dfbcf7c96 100644 --- a/topics/client-bearer-auth.md +++ b/topics/client-bearer-auth.md @@ -41,7 +41,7 @@ val client = HttpClient(CIO) { ### Load tokens -Use the `loadTokens` callback to provide the initial access and refresh tokens. Typically, this callback loads cached +Use the `loadTokens {}` callback to provide the initial access and refresh tokens. Typically, this callback loads cached tokens from local storage and returns them as a `BearerTokens` instance. ```kotlin @@ -64,7 +64,7 @@ Authorization: Bearer abc123 ### Refresh tokens -Use the `refreshTokens` callback to define how the client obtains new tokens when the current access token becomes +Use the `refreshTokens {}` callback to define how the client obtains new tokens when the current access token becomes invalid: ```kotlin @@ -83,15 +83,20 @@ The refresh process works as follows: 1. The client makes a request to a protected resource using an invalid access token. 2. The resource server returns a `401 Unauthorized` response. - > If [several providers](client-auth.md#realm) are installed, a response should have the `WWW-Authenticate` header. -3. The client automatically invokes `refreshTokens` to obtain new tokens. +3. The client automatically invokes the `refreshTokens {}` callback to obtain new tokens. 4. The client retries the request to a protected resource using the new token. +> If [several providers](client-auth.md#realm) are installed, a response should have the `WWW-Authenticate` header. +> If the client installs only one authentication provider, Ktor attempts that provider for `401 Unauthorized` responses +> even when the `WWW-Authenticate` header is missing or specifies a different scheme. +> +{style="tip"} + ### Send credentials without waiting for 401 By default, the client sends credentials only after receiving a `401 Unauthorized` response. -You can override this behavior using the `sendWithoutRequest` callback function. This callback determines whether the +You can override this behavior using the `sendWithoutRequest {}` callback function. This callback determines whether the client should attach credentials before sending the request. For example, the following configuration always sends the token when accessing Google APIs: @@ -219,7 +224,7 @@ class looks as follows: #### Store tokens {id="step4"} -Once the tokens are received, store them so they can be supplied to the `loadTokens` and `refreshTokens` callbacks. In +Once the tokens are received, store them so they can be supplied to the `loadTokens {}` and `refreshTokens {}` callbacks. In this example, the storage is a mutable list of `BearerTokens`: ```kotlin @@ -245,7 +250,7 @@ Before doing that, configure the client to use bearer authentication: The following settings are specified: * The `loadTokens` callback retrieves tokens from [storage](#step4). -* The `sendWithoutRequest` callback sends the access token without waiting for the `401 Unauthorized` response when +* The `sendWithoutRequest {}` callback sends the access token without waiting for the `401 Unauthorized` response when calling the Google API. With this client, you can now make a request to the protected resource: @@ -279,29 +284,31 @@ At some point, the client repeats the request from [Step 5](#step5), but with an #### 401 Unauthorized response {id="step8"} When the token is no longer valid, the resource server returns a `401 Unauthorized` response. The client then invokes -the `refreshTokens` callback, which is responsible for obtaining new tokens. +the `refreshTokens {}` callback, which is responsible for obtaining new tokens. -> The `401` response returns JSON data with error details. This needs to be [handled when receiving a response](#step12). +> The `401 Unauthorized` response returns JSON data with error details. This needs to be [handled when receiving a response](#step12). +> +{style="tip"} #### Refresh the access token {id="step9"} -To obtain a new access token, configure `refreshTokens` to make another request to the token endpoint. This -time, use the `refresh_token` grant type instead of `authorization_code`: +To obtain a new access token, configure the `refreshTokens {}` callback to make another request to the token endpoint. +This time, use the `refresh_token` grant type instead of `authorization_code`: ```kotlin ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/Application.kt" include-lines="43-44,48-56,59,63-64"} -The `refreshTokens` callback uses `RefreshTokensParams` as a receiver and allows you to access the following settings: -- The `client` instance, which can be used to submit form parameters. -- The `oldTokens` property is used to access the refresh token and send it to the token endpoint. +The `refreshTokens {}` callback uses `RefreshTokensParams` as a receiver and allows you to access the following settings: +* The `client` instance, which can be used to submit form parameters. +* The `oldTokens` property is used to access the refresh token and send it to the token endpoint. +* The `.markAsRefreshTokenRequest()` function exposed by `HttpRequestBuilder` marks the request for refreshing auth + tokens, resulting in a special handling of it. -> The `markAsRefreshTokenRequest` function exposed by `HttpRequestBuilder` enables special handling of requests used -> to obtain a refresh token. #### Save refreshed tokens {id="step10"} -After receiving new tokens, save them in the [token storage](#step4). With this, the `refreshTokens` callback +After receiving new tokens, save them in the [token storage](#step4). With this, the `refreshTokens {}` callback looks as follows: ```kotlin @@ -318,8 +325,8 @@ With the refreshed access token stored, the next request to the protected resour #### Handle API errors {id="step12"} -Given that the [`401` response](#step8) returns JSON data with error details, update the example to read error responses -as an `ErrorInfo` object: +Given that the [`401 Unauthorized` response](#step8) returns JSON data with error details, update the example to read +error responses as an `ErrorInfo` object: ```kotlin ``` @@ -331,8 +338,9 @@ The `ErrorInfo` class is defined as follows: ``` {src="snippets/client-auth-oauth-google/src/main/kotlin/com/example/models/ErrorInfo.kt" include-lines="3-13"} -For the full example, see [client-auth-oauth-google](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/client-auth-oauth-google). - +> For the full example, see [client-auth-oauth-google](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/client-auth-oauth-google). +> +{style="tip"} From 0a77c406b876f13a54b0b96912b0d88f95015360 Mon Sep 17 00:00:00 2001 From: vnikolova Date: Mon, 16 Mar 2026 14:53:38 +0100 Subject: [PATCH 3/4] KTOR-8022 Add dependencies sections in client auth topics --- topics/client-basic-auth.md | 4 +++- topics/client-bearer-auth.md | 2 ++ topics/client-digest-auth.md | 2 ++ topics/lib.topic | 6 +++--- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/topics/client-basic-auth.md b/topics/client-basic-auth.md index 1902c677e..17be8abdd 100644 --- a/topics/client-basic-auth.md +++ b/topics/client-basic-auth.md @@ -8,10 +8,12 @@ -The Basic [authentication scheme](client-auth.md) can be used for logging in users. In this scheme, user credentials are transmitted as username/password pairs encoded using Base64. +The basic [authentication scheme](client-auth.md) can be used for logging in users. In this scheme, user credentials are transmitted as username/password pairs encoded using Base64. > On the server, Ktor provides the [Authentication](server-basic-auth.md) plugin for handling basic authentication. + + ## Basic authentication flow {id="flow"} The basic authentication flow looks as follows: diff --git a/topics/client-bearer-auth.md b/topics/client-bearer-auth.md index dfbcf7c96..98d078430 100644 --- a/topics/client-bearer-auth.md +++ b/topics/client-bearer-auth.md @@ -18,6 +18,8 @@ the Ktor server documentation](server-oauth.md#flow). > On the server, Ktor provides the [Authentication](server-bearer-auth.md) plugin for handling bearer authentication. + + ## Configure bearer authentication {id="configure"} A Ktor client allows you to send a token in the `Authorization` header using the `Bearer` scheme. You diff --git a/topics/client-digest-auth.md b/topics/client-digest-auth.md index c0af4d23f..d33549603 100644 --- a/topics/client-digest-auth.md +++ b/topics/client-digest-auth.md @@ -13,6 +13,8 @@ network. > On the server, Ktor provides the [Authentication](server-digest-auth.md) plugin for handling digest authentication. + + ## Digest authentication flow {id="flow"} The digest authentication flow looks as follows: diff --git a/topics/lib.topic b/topics/lib.topic index 6ce156039..f10a0f9f1 100644 --- a/topics/lib.topic +++ b/topics/lib.topic @@ -65,9 +65,9 @@ -

- You can learn more about artifacts required by the Ktor client from . -

+ + To learn more about artifacts required by the Ktor client, see . + From 45bff20d2316cd262f02421a892f3218dce1aa86 Mon Sep 17 00:00:00 2001 From: vnikolova Date: Mon, 16 Mar 2026 15:07:26 +0100 Subject: [PATCH 4/4] KTOR-8846 Document refreshTokens behavior in multiple parallel requests --- topics/client-bearer-auth.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/topics/client-bearer-auth.md b/topics/client-bearer-auth.md index 98d078430..8416f11bd 100644 --- a/topics/client-bearer-auth.md +++ b/topics/client-bearer-auth.md @@ -88,6 +88,10 @@ The refresh process works as follows: 3. The client automatically invokes the `refreshTokens {}` callback to obtain new tokens. 4. The client retries the request to a protected resource using the new token. +When multiple requests fail with `401 Unauthorized` at the same time, the client performs the token refresh only once. +The first request that receives the `401` response triggers the `refreshTokens {}` callback. Other requests wait for the +refresh operation to complete and are then retried with the new token. + > If [several providers](client-auth.md#realm) are installed, a response should have the `WWW-Authenticate` header. > If the client installs only one authentication provider, Ktor attempts that provider for `401 Unauthorized` responses > even when the `WWW-Authenticate` header is missing or specifies a different scheme.