Ktor 2.3.10 Help

OAuth

OAuth is an open standard for access delegation. OAuth can be used to authorize users of your application by using external providers, such as Google, Facebook, Twitter, and so on.

The oauth provider supports the authorization code flow. You can configure OAuth parameters in one place, and Ktor will automatically make a request to a specified authorization server with the necessary parameters.

Add dependencies

To use OAuth, you need to include the ktor-server-auth artifact in the build script:

implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation "io.ktor:ktor-server-auth:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-auth-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Install the Sessions plugin

To avoid requesting authorization every time a client tries to access a protected resource, you can store the access token in the session upon successful authorization. You can then retrieve the access token from the current session within the handler of the protected route and use it to request the resource.

import io.ktor.server.sessions.* fun Application.main(httpClient: HttpClient = applicationHttpClient) { install(Sessions) { cookie<UserSession>("user_session") } } data class UserSession(val state: String, val token: String)

OAuth authorization flow

The OAuth authorization flow in a Ktor application might look as follows:

  1. A user opens a login page in a Ktor application.

  2. Ktor makes an automatic redirect to the authorization page for a specific provider and passes the necessary parameters:

    • A client ID used to access APIs of the selected provider.

    • A callback or redirect URL specifying a Ktor application page that will be opened after authorization is completed.

    • Scopes of third-party resources required for a Ktor application.

    • A grant type used to get an access token (Authorization Code).

    • A state parameter used to mitigate CSRF attacks and redirect users.

    • Optional parameters specific for a certain provider.

  3. The authorization page shows a consent screen with the level of permissions required for a Ktor application. These permissions depend on the specified scopes, as configured in Step 2: Configure the OAuth provider.

  4. If a user approves the requested permissions, the authorization server redirects back to the designated redirect URL and sends the authorization code.

  5. Ktor makes one more automatic request to the specified access token URL, including the following parameters:

    • Authorization code.

    • Client ID and client secret.

    The authorization server responds by returning an access token.

  6. The client can then use this token to make a request to the required service of the selected provider. In most cases, a token will be sent in the Authorization header using the Bearer schema.

  7. The service validates the token, uses its scope for authorization, and returns the requested data.

Install OAuth

To install the oauth authentication provider, call the oauth function inside the install block. Optionally, you can specify a provider name. For example, to install an oauth provider with the name "auth-oauth-google" it would look like the following:

import io.ktor.server.application.* import io.ktor.server.auth.* fun Application.main(httpClient: HttpClient = applicationHttpClient) { install(Authentication) { oauth("auth-oauth-google") { // Configure oauth authentication } } }

Configure OAuth

This section demonstrates how to configure the oauth provider for authorizing users of your application using Google. For the complete runnable example, see auth-oauth-google.

Prerequisites: Create authorization credentials

To access Google APIs, you need to create authorization credentials in the Google Cloud Console.

  1. Open the Credentials page in the Google Cloud Console.

  2. Click CREATE CREDENTIALS and choose OAuth client ID.

  3. Choose Web application from the dropdown.

  4. Specify the following settings:

    • Authorised JavaScript origins: http://localhost:8080.

    • Authorised redirect URIs: http://localhost:8080/callback. In Ktor, the urlProvider property is used to specify a redirect route that will be opened when authorization is completed.

  5. Click CREATE.

  6. In the invoked dialog, copy the created client ID and client secret that will be used to configure the oauth provider.

Step 1: Create the HTTP client

Before configuring the oauth provider, you need to create the HttpClient that will be used by the server to make requests to the OAuth server. The ContentNegotiation client plugin with the JSON serializer is required to deserialize received JSON data after a request to the API.

val applicationHttpClient = HttpClient(CIO) { install(ContentNegotiation) { json() } }

The client instance is passed to the main module function to have the capability to create a separate client instance in a server test.

fun Application.main(httpClient: HttpClient = applicationHttpClient) { }

Step 2: Configure the OAuth provider

The code snippet below shows how to create and configure the oauth provider with the auth-oauth-google name.

val redirects = mutableMapOf<String, String>() install(Authentication) { oauth("auth-oauth-google") { // Configure oauth authentication urlProvider = { "http://localhost:8080/callback" } providerLookup = { OAuthServerSettings.OAuth2ServerSettings( name = "google", authorizeUrl = "https://accounts.google.com/o/oauth2/auth", accessTokenUrl = "https://accounts.google.com/o/oauth2/token", requestMethod = HttpMethod.Post, clientId = System.getenv("GOOGLE_CLIENT_ID"), clientSecret = System.getenv("GOOGLE_CLIENT_SECRET"), defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"), extraAuthParameters = listOf("access_type" to "offline"), onStateCreated = { call, state -> //saves new state with redirect url value call.request.queryParameters["redirectUrl"]?.let { redirects[state] = it } } ) } client = httpClient }
  • The urlProvider specifies a redirect route that will be invoked when authorization is completed.

  • providerLookup allows you to specify OAuth settings for a required provider. These settings are represented by the OAuthServerSettings class and allow Ktor to make automatic requests to the OAuth server.

  • The client property specifies the HttpClient used by Ktor to make requests to the OAuth server.

Step 3: Add a login route

After configuring the oauth provider, you need to create a protected login route inside the authenticate function that accepts the name of the oauth provider. When Ktor receives a request to this route, it will be automatically redirected to authorizeUrl defined in providerLookup.

routing { authenticate("auth-oauth-google") { get("/login") { // Redirects to 'authorizeUrl' automatically } } }

A user will see the authorization page with the level of permissions required for a Ktor application. These permissions depend on defaultScopes specified in providerLookup.

Step 4: Add a redirect route

Apart from the login route, you need to create the redirect route for the urlProvider, as specified in Step 2: Configure the OAuth provider.

Inside this route you can retrieve the OAuthAccessTokenResponse object using the call.principal function. OAuthAccessTokenResponse allows you to access a token and other parameters returned by the OAuth server.

routing { authenticate("auth-oauth-google") { get("/login") { // Redirects to 'authorizeUrl' automatically } get("/callback") { val currentPrincipal: OAuthAccessTokenResponse.OAuth2? = call.principal() // redirects home if the url is not found before authorization currentPrincipal?.let { principal -> principal.state?.let { state -> call.sessions.set(UserSession(state, principal.accessToken)) redirects[state]?.let { redirect -> call.respondRedirect(redirect) return@get } } } call.respondRedirect("/home") } } }

In this example, the following actions are performed after receiving a token:

  • The token is saved in a Session, which content can be accessed inside other routes.

  • The user is redirected to the next route where a request to Google API is made.

  • If the requested route is not found, the user is redirected to the /home route.

Step 5: Make a request to API

After receiving a token inside the redirect route and saving it to a session, you can make the request to external APIs using this token. The code snippet below shows how to use the HttpClient to make such a request and get a user's information by sending this token in the Authorization header.

Create a new function called getPersonalGreeting which will make the request and return the response body:

private suspend fun getPersonalGreeting( httpClient: HttpClient, userSession: UserSession ): UserInfo = httpClient.get("https://www.googleapis.com/oauth2/v2/userinfo") { headers { append(HttpHeaders.Authorization, "Bearer ${userSession.token}") } }.body()

Then, you can call the function within a get route to retrieve a user's information:

get("/{path}") { val userSession: UserSession? = getSession(call) if (userSession != null) { val userInfo: UserInfo = getPersonalGreeting(httpClient, userSession) call.respondText("Hello, ${userInfo.name}!") } }

For the complete runnable example, see auth-oauth-google.

Last modified: 02 April 2024