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-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 68d2dfbdb..8416f11bd 100644
--- a/topics/client-bearer-auth.md
+++ b/topics/client-bearer-auth.md
@@ -1,6 +1,6 @@
[//]: # (title: Bearer authentication in Ktor Client)
-
+
@@ -10,207 +10,244 @@
+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.
+
+To configure bearer authentication, install the `Auth` plugin and configure the `bearer` provider:
- ```kotlin
+```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.
+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.
- c. The client makes one more request to a protected resource automatically using a new token this time.
+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.
-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.
+> 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"}
- ```kotlin
- install(Auth) {
- bearer {
- // Load and refresh tokens ...
- sendWithoutRequest { request ->
- request.url.host == "www.googleapis.com"
- }
+### 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
+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
-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:
+Use the `cacheTokens` property to control whether bearer tokens are cached between requests.
+
+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:
-The next sections explain how each step is implemented and how the `Bearer` authentication provider assists in
-accessing the API.
+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).
-### (1) -> Authorization request {id="step1"}
+The following sections explain how the Ktor client implements each step.
-The first step is to construct the authorization URL used to request the necessary permissions. This is done by appending
+#### Authorization request {id="step1"}
+
+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
+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
```
{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 +255,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 +266,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,36 +283,38 @@ 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 `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"}
-### (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 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.
-### (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,17 +322,17 @@ 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
-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
```
@@ -309,8 +344,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"}
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 .
+