Bearer authentication
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
bearer
function inside theinstall
block.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
loadTokens
callback. This callback is intended to load cached tokens from a local storage and return them as theBearerTokens
instance.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 in theAuthorization
header using theBearer
scheme: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
refreshTokens
automatically 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 ID
credentials with theAndroid
application 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:8080
value indicates that the Loopback IP address flow is used to get the authorization code.access_type
: The access type is set tooffline
since 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
json
serializer is required to deserialize user information received from a resource server in a JSON format.The Auth plugin with the
bearer
provider is configured as follows:The
loadTokens
callback loads tokens from the storage.The
sendWithoutRequest
callback 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
client
instance. In the code snippet above, we use it to submit form parameters.The
oldTokens
property 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.