Computer scienceBackendKtorKtor Plugins

OAuth in Ktor

5 minutes read

In today's world, where users often use multiple websites and applications, having a secure and easy-to-use authentication system is essential. OAuth provides a solution to this problem. Using OAuth in Ktor, you can easily provide authentication and authorization for your APIs. In this topic, we'll explore how to implement OAuth in Ktor and provide a seamless authentication experience for your users.

What is OAuth?

Let's explore how OAuth works using a simple example.

Imagine that you have created a website called "Awesome site" with some useful features, and you want your users to authorize, but you don't want them to spend a lot of time filling out authentication forms. To streamline the process, you decide to use OAuth to allow users to log in using their Google accounts.

Here are the approximate steps of the user authorization process:

  1. The user visits the "Awesome site" that asks them to sign in, so the user clicks on the "Sign in with Google" button.

  2. "Awesome site" sends a request to Google's OAuth server, requesting authorization to access the user's data.

  3. Google prompts the user to enter their Google login credentials if they are not already logged in.

  4. The user enters their Google login credentials (only if the user is not already logged in), and Google authenticates the user.

  5. Google prompts the user to grant or deny permission to "Awesome site" to access certain information from their Google account, such as their name and email address.

  6. The user grants permission to "Awesome site".

  7. Google generates an access token and sends it to "Awesome site".

  8. "Awesome site" uses the access token to make requests to Google's APIs to retrieve the user's information, such as their name and email address.

  9. "Awesome site" creates a new user account, or logs in to an existing user account, with the user information received from Google.

  10. The user is now logged in to the "Awesome site" with their Google account.

In summary, OAuth enables the "Awesome site" to access the user's Google account data without the need for the user to share their Google login credentials with "Awesome site". This process provides a secure and convenient way for users to authenticate and access the website.

Install OAuth

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

implementation("io.ktor:ktor-server-auth:$ktor_version")

Then install the OAuth authentication provider, by calling oauth function inside the install block:

import io.ktor.server.application.*
import io.ktor.server.auth.*
//...
install(Authentication) {
   oauth ("auth-oauth-google") { // "auth-oauth-google" is optional name parameter
        // Configure oauth authentication
    }
}

Creating authorization credentials

Before your site or application can authorize someone using OAuth, it must first authenticate with an OAuth provider. In this topic we will cover how to create the necessary credentials on the Google API Console.

First, you need to create your project on Google Cloud:

Creating of Google Cloud project.

Then you should configure the consent screen for your project:

Configuring Google OAuth consent screen: choosing User Type.

We recommend selecting the external user type.

Then add the minimum required information about your application that the user will see (you can always add more later if desired):

Configuring Google OAuth consent screen: filling in the remaining data.

And the final mandatory step in setting up the consent screen is configuring the permissions you want the user to grant you. To do this, select all the necessary scopes:

Configuring Google OAuth consent screen: choosing the permissions you want the user to grant you

For our educational purposes, the first two from the list will be sufficient.

To create credentials for our project, follow these steps:

  1. Go to the Credentials tab and click on "Create Credentials"

  2. Select "OAuth Client ID"

  3. Choose "Web Application" as the application type.

  4. Specify http://localhost:8080 as the Authorized JavaScript origins and http://localhost:8080/callback as the Authorized redirect URIs

  5. Click on the "CREATE" button.

In the pop-up window, you will see the data for the credentials you just created. Next, we will need the "Client ID" and "Client Secret". You can copy them immediately or view them later by clicking on the name of the created credentials.

Configuring OAuth in Ktor

First, let's create a simple HttpClient that our server will use to make requests to the Google OAuth server:

val applicationHttpClient = HttpClient(Apache)

Then configure the OAuth provider that we previously created:

oauth("auth-oauth-google") {
    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")
        )
    }
    client = applicationHttpClient
}

The urlProvider should be the same as we specified in our Google credentials. It will be opened when the authorization is complete.

As a client, we use our HttpClient, as we say it will make requests to the Google server.

The providerLookup is our OAuth settings where we configure OAuth for the required provider. Let's consider it in more detail:

  • name is just the name of OAuth provider.

  • authorizeUrl is the authorization page of OAuth provider. You can find it in the OAuth provider's documentation.

  • accessTokenUrl is used to request the token from OAuth provider.

  • requestMethod is the method used to receive an access token. It is POST in our example, but it may vary depending on OAuth provider and authorization flow.

  • cliendId and ClientSecret are information about our credentials on Google Cloud. You can also hardcode them instead of creating environment variables.

  • defaultScopes are the OAuth sopes used by default.

There are more OAuth settings that you can explore on your own, but these are enough to figure out how OAuth works.

Finally, we need to configure our routing:

install(Sessions) {
    cookie<UserSession>("MY_SESSION")
}

routing {
    authenticate("auth-oauth-google") {
        get("/login") {
            // Redirects to Google authorization page automatically
        }

        get("/callback") {
            val principal: OAuthAccessTokenResponse.OAuth2? = call.authentication.principal()
            call.sessions.set(UserSession(principal?.accessToken.toString()))
            call.respondRedirect("/hello")
        }
    }

    get("/hello") {
        val userSession: UserSession? = call.sessions.get()
        val userInfo: String = applicationHttpClient.get("https://www.googleapis.com/oauth2/v2/userinfo") {
            headers { append(HttpHeaders.Authorization, "Bearer ${userSession!!.accessToken}") }        
        }.body()
        call.respondText("This is your userInfo: $userInfo")
    }
}

Here we install the Sessions plugin to access to acessToken from different routes. We use a simple data class to store the token:

data class UserSession(val accessToken: String)

When we open the "/login" route in the browser, the server automatically redirects us to the authorizeUrl ("https://accounts.google.com/o/oauth2/auth") using the clientId and clientSecret. We should see the consent screen of our CoolProject. When we enter the email and password, our server receives the accessToken from the Google OAuth server.

Then we are redirected to the urlProvider ("/callback" route). Here, we pass the accessToken to the UserSession and then redirect to the "/hello" route.

Inside the "/hello" route we get an instance of UserSession with the accessToken and use this token in the Authorization header to make a request to the Google server and retrieve the desired user information. Here, we don't parse the JSON body of the response for simplicity, but you just need to install ContentNegotiation in your HttpClient to do it:

This is your userInfo: {
  "id": "111111111111111111111",
  "name": "John Matrix",
  "given_name": "John",
  "family_name": "Matrix",
  "picture": "https://lh3.googleusercontent.com/a/AGNmyxZdqjmsmXgyfPH2ua2mQr6WLS9XL98cJM2X_WA=s83-c-mo",
  "locale": "en"
}

That's it!

Now we can use this information without bothering the user with inventing new credentials and filling out unfamiliar forms.

Conclusion

OAuth is an authorization framework that enables websites and apps to grant access to user data without sharing credentials.

By delegating authentication to trusted providers, developers can simplify user authorization and enhance security. With OAuth, users can securely authorize apps using their existing accounts, eliminating the need for separate credentials.

Ktor provides built-in OAuth support, making it easy to integrate OAuth authentication into applications without relying on external libraries or complex configurations.

3 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo