Access and Refresh Tokens Using KTOR

Access and Refresh Tokens Using KTOR

Asim Latif's photo
May 19, 2024·

2 min read

In addition to the conventional method of handling access and refresh tokens by monitoring response status codes and initiating a refresh token request upon encountering a 401 (Unauthorized) error, Ktor provides a distinct centralized approach within its client configuration.

Before moving forward, let's setup with required dependencies.

dependencies {
    implementation "io.ktor:ktor-client-android:$ktor_version"
    implementation "io.ktor:ktor-client-json:$ktor_version"
    implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"
    implementation("io.ktor:ktor-client-auth:$ktor_version")
}

In order to support serialization, apply kotlinx serialization plugin. You can find the implementation here.

In order to manage access and refresh token, we need a Token Manager. Token Manager is a class which is being used to store and retrieve access and refresh token. On first successful sign in or sign up, we store the tokens using this manager.

You can have your own implementation for token manager with room or shared preferences whatever you prefer. But, I can provide you with example interface look like this:

interface TokenManager {
    suspend fun update(access: String, refresh:String)
    fun getAccess():String
    fun getRefresh():String
}

The next step is to create our HttpClientProvider. We will use Ktor's Auth Plugin here. You can read more about this here

class ClientProviderr(
    private val tokenManager: TokenManager
){
    private var client: HttpClient = create()

    private val accessToken = tokenManager.getAccess()
    private val refreshToken = tokenManager.getRefresh()

    fun instance() = client

    private fun create() = HttpClient {
        expectSuccess = true

        defaultRequest {
            header(HttpHeaders.ContentType, ContentType.Application.Json)
        }

        install(Auth) {
            bearer {
                sendWithoutRequest { request ->
                    request.url.host == "url_not_require_authentication.com"
                }

                loadTokens {
                    BearerTokens(accessToken, refreshToken)
                }

                refreshTokens {
                    val accessToken: String = client.submitForm(
                        url = "base-url/refresh-token",
                        formParameters = Parameters.build {
                            append("refresh_token", refreshToken)
                        }
                    ) { markAsRefreshTokenRequest() }.body()

                    tokenManager.update(accessToken, refreshToken)

                    BearerTokens(
                        accessToken = accessToken,
                        refreshToken = refreshToken
                    )
                }
            }
        }


        install(ContentNegotiation) {
            json(Json)
        }

        install(HttpTimeout) {
            requestTimeoutMillis = 120000
            connectTimeoutMillis = 120000
        }
    }
}

Auth Plugin:

In Ktor Client Configuration, we have used the Auth Plugin which has block bearer where we have to specify these tokens using BearerTokens Method. Also, we have to define our mechanism to automatically make request and update tokens in our manager using the provided block of refreshTokens*.*

The important thing here is that some hosts may not require the authentication, In that case you can specify those hosts in sendWithoutRequest block.

So, by using this approach, whenever server returns 401, Ktor will make another request to get updated token and make another request.

Summary

So, by using this client you can make requests and don't need to worry about the unauthorized response. Ktor and Token Manager will handle it for you.