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.
Optionally, 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" } } }
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
As the first step, we need to obtain client credentials required for accessing Google APIs:
Create a Google account.
Open the Google Cloud Console and create the
OAuth client IDcredentials with theAndroidapplication type. This client ID will be used to obtain an authorization grant.
OAuth authorization flow
The OAuth authorization flow for our application looks as follows:
Let's investigate how each step is implemented and how the Bearer authentication provider helps us access the API.
(1) -> Authorization request
As the first step, we need to build the authorization link that is used to request the desired permissions. To do this, we need to append specified query parameters to the URL:
client_id: a client ID obtained earlier is used to access Google APIs.scope: scopes of resources required for a Ktor application. In our case, the application requests information about a user's profile.response_type: a grant type used to get an access token. In our case, we need 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: The access type is set toofflinesince our console application needs to refresh access tokens when the user is not present at the browser.
(2) <- Authorization grant (code)
At this step, we copy the authorization code from the browser, paste it in a console, and save it in a variable:
(3) -> Authorization grant (code)
Now we are ready to exchange the authorization code for tokens. To do this, we need to 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, we 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
When tokens are received, we can save them in a storage. In our example, a storage is a mutable list of BearerTokens instances. This means that we can pass its elements to the loadTokens and refreshTokens callbacks.
(5) -> Request with valid token
Now we have valid tokens, so we can make a request to the protected Google API and get information about a user. First, we 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 is configured to send credentials without waiting for the401(Unauthorized) response only to a host providing access to protected resources.
This client can be used to make a request to the protected resource:
(6) <- Protected resource
The resource server returns information about a user in a JSON format. We can deserialize the response into the UserInfo class instance and show a personal greeting:
The UserInfo class looks as follows:
(7) -> Request with expired token
At some point, the client makes a request as in Step 5 but with the expired access token.
(8) <- 401 Unauthorized response
The resource server returns the 401 unauthorized response, so the client should invoke the refreshTokens callback.
(9) -> Authorization grant (refresh token)
To obtain a new access token, we need to configure refreshTokens and make another request to the token endpoint. This time, we use the refresh_token grant type instead of authorization_code:
Note that the refreshTokens callback uses RefreshTokensParams as a receiver and allows you to access the following settings:
The
clientinstance. In the code snippet above, we use it 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, we can save them in the storage, so refreshTokens looks as follows:
(11) -> Request with new token
At this step, the request to the protected resource contains a new token and should work fine.
(12) <-- Protected resource
Given that the 401 response returns JSON data with error details, we need to update our sample to receive the information about an error as a ErrorInfo object:
The ErrorInfo class looks as follows:
You can find the full example here: client-auth-oauth-google.