Bearer authentication in Ktor Client
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 section for a Ktor server.
Configure bearer authentication
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:
Call the
bearerfunction inside theinstallblock.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 } } }Configure how to obtain the initial access and refresh tokens using the
loadTokenscallback. This callback is intended to load cached tokens from a local storage and return them as theBearerTokensinstance.install(Auth) { bearer { loadTokens { // Load tokens from a local storage and return them as the 'BearerTokens' instance BearerTokens("abc123", "xyz111") } } }The
abc123access token is sent with each request in theAuthorizationheader using theBearerscheme:GET http://localhost:8080/ Authorization: Bearer abc123Specify how to obtain a new token if the old one is invalid using
refreshTokens.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.b. The client calls
refreshTokensautomatically to obtain new tokens.c. The client makes one more request to a protected resource automatically using a new token this time.
(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.install(Auth) { bearer { // Load and refresh tokens ... sendWithoutRequest { request -> request.url.host == "www.googleapis.com" } } }(Optional) Use the
cacheTokensoption 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:install(Auth) { bearer { cacheTokens = false // Reloads tokens for every request loadTokens { loadDynamicTokens() } } }
Example: Using Bearer authentication to access Google API
Let's take a look at how to use bearer authentication to access Google APIs, which use the OAuth 2.0 protocol for authentication and authorization. We'll investigate the client-auth-oauth-google console application that gets Google's profile information.
Obtain client credentials
To access Google APIs, you first need OAuth client credentials:
Create or sign in to a Google account.
Open the Google Cloud Console and create an
OAuth client IDwith theAndroidapplication type. This client ID will be used to obtain an authorization grant.
OAuth authorization flow
The OAuth authorization flow looks as follows:
The next sections explain how each step is implemented and how the Bearer authentication provider assists in accessing the API.
(1) -> Authorization request
The first step is to construct the authorization URL used to request the necessary permissions. This is done by appending the required query parameters:
client_id: the client ID obtained earlier 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.redirect_uri: thehttp://127.0.0.1:8080value indicates that the Loopback IP address flow is used to get the authorization code.access_type: set toofflineso that the application can refresh access tokens when the user is not present at the browser.
(2) <- Authorization grant (code)
Copy the authorization code from the browser, paste it in a console, and save it in a variable:
(3) -> Authorization grant (code)
Next, exchange the authorization code for tokens. To do this, create a client and install the ContentNegotiation plugin with the json serializer. This serializer is required to deserialize tokens received from the Google OAuth token endpoint.
Using the created client, you can securely pass the authorization code and other necessary options to the token endpoint as form parameters:
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:
(4) <- Access and refresh tokens
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:
(5) -> Request with valid token
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:
The following settings are specified:
The already installed ContentNegotiation plugin with the
jsonserializer is required to deserialize user information received from a resource server in a JSON format.The Auth plugin with the
bearerprovider is configured as follows:The
loadTokenscallback loads tokens from the storage.The
sendWithoutRequestcallback sends the access token without waiting for the401 Unauthorizedresponse when accessing Google's protected API.
With this client, you can now make a request to the protected resource:
(6) <- Protected resource
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:
The UserInfo class looks as follows:
(7) -> Request with expired token
At some point, the client repeats the request from Step 5, but with an expired access token.
(8) <- 401 Unauthorized response
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.
(9) -> Authorization grant (refresh token)
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:
The refreshTokens callback uses RefreshTokensParams as a receiver and allows you to access the following settings:
The
clientinstance, which can be used to submit form parameters.The
oldTokensproperty is used to access the refresh token and send it to the token endpoint.
(10) <- Access and refresh tokens
After receiving new tokens, they need to be saved in the token storage. With this, the refreshTokens callback looks as follows:
(11) -> Request with new token
With the refreshed access token stored, the next request to the protected resource should succeed:
(12) <-- Protected resource
Given that the 401 response returns JSON data with error details, update the example to read error responses as an ErrorInfo object:
The ErrorInfo class is defined as follows:
For the full example, see client-auth-oauth-google.