Skip to main content

Authorization and retrying of web requests for OkHttp and Retrofit

Updated:

Are you trying to set up authorization for your OkHttp or Retrofit web requests but aren't sure of the best way to do it? Have you explored Interceptor and Authenticator but aren't sure which to use or how best to use them? We will explore these concepts within OkHttp, how to sign web requests and also how to retry them if they fail due to a failed authorization attempt.

When setting up networking in our apps, authorization is almost always required, as not many remote APIs are completely un-authenticated. OAuth is a common system to use, relying on access tokens to protect our endpoints and refresh tokens to obtain new access tokens once they have expired. The idea is that the access token is added as an Authorization HTTP header on requests to let the API know we have access to a particular resource.

OkHttp provides Interceptors which can alter web requests before they are sent out and Authenticators that allow us to re-sign and retry requests that have failed due to authorization. It is very common, particularly on Android, to use Retrofit for networking, which uses OkHttp internally and so the same techniques apply to Retrofit as well.

Let's have a look at how we set this up and how it all works!

Signing requests

We will start by setting up our AuthorizationInterceptor that adds the Authorization header to all web requests that are sent off to our remote API.

Our access tokens are provided by AuthorizationRepository, which either provides a stored access token or obtains a new one if the stored one has already expired. How this process works depends on the authorization each particular API uses, the important part here is that AuthorizationInterceptor has a function it can call to get an access token to sign the web request with.

Our Interceptor implementation needs to provide an intercept function that can extract the current Request, transform it and then pass this new request back into the chain. The transformation we need to apply involves adding an Authorization header that signs the request with our access token. It is worth noting that the exact format of the header may change depending on what type of tokens each API uses, in our example Bearer tokens are used which are pretty common.

class AuthorizationInterceptor(
    private val authorizationRepository: AuthorizationRepository
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val newRequest = chain.request().signedRequest()
        return chain.proceed(newRequest)
    }

    private fun Request.signedRequest(): Request {
        val accessToken = authorizationRepository.fetchFreshAccessToken()
        return newBuilder()
            .header("Authorization", "Bearer ${accessToken.rawToken}")
            .build()
    }
}

Registering our interceptor just involves adding it to the builder we are using to create our OkHttpClient for either use directly or with Retrofit.

fun okHttpClient(authorizationInterceptor: AuthorizationInterceptor) =
    OkHttpClient.Builder()
        .addInterceptor(authorizationInterceptor)
        .build()

All outgoing web requests will now be signed with our access token.

Retrying failed requests

It is possible that our requests may fail with 401 Unauthorized due to an issue with the access token we provided. This will usually be due to the access token having expired or being revoked on the server-side. To handle this situation we can build an OkHttp Authenticator, that allows us to catch this case, add a new token and then retry the request.

We will start by extracting an extension function to set the Authorization header on a request.

fun Request.signWithToken(accessToken: AccessToken) =
    newBuilder()
        .header("Authorization", "Bearer ${accessToken.rawToken}")
        .build()

We will build in a limit on the number of retries to avoid an infinite loop if authorization for a particular endpoint fails entirely. We can determine how many times a request has been retried from the response.

private val Response.retryCount: Int
    get() {
        var currentResponse = priorResponse
        var result = 0
        while (currentResponse != null) {
            result++
            currentResponse = currentResponse.priorResponse
        }
        return result
    }

The implementation of Authenticator receives the response that failed with 401 Unauthorized and has to optionally provide a new request to be triggered. If our request has already been retried twice we will simply allow it to fail to avoid an infinite loop. Signing the request is done in the same way as in our AuthorizationInterceptor above.

class TokenRefreshAuthenticator(
    private val authorizationRepository: AuthorizationRepository
) : Authenticator {
    override fun authenticate(
        route: Route?,
        response: Response
    ): Request? = when {
        response.retryCount > 2 -> null
        else -> response.createSignedRequest()
    }

    private fun Response.createSignedRequest(): Request? = try {
        val accessToken = authenticationRepository.fetchFreshAccessToken()
        request.signWithToken(accessToken)
    } catch (error: Throwable) {
        Logger.error(error, "Failed to re-sign request")
        null
    }
}

Registering our authenticator involves adding it to the builder alongside our interceptor.

OkHttpClient.Builder()
    .addInterceptor(authorizationInterceptor)
    .authenticator(tokenRefreshAuthenticator)
    .build()

Web requests that fail with authorization issues will now be re-signed and retried!

Refreshing expired tokens

In the above AuthorizationInterceptor and TokenRefreshAuthenticator we obtain an access token using fetchFreshAccessToken. This function has been built to return the stored access token if it hasn't expired yet or to obtain a new one if it has.

An important note is that the function signatures of both Interceptor and Authenticator require the request to be created or transformed synchronously. The process of refreshing an access token is likely asynchronous due to requiring its own web request to an OAuth API. We therefore need to call this asynchronous token refresh process synchronously, exactly how will depend on how on it is implemented.

Multiple authorization types

In our implementation so far we have assumed that all requests need to be signed in the same way, however, in reality this likely won't be the case. For example, there will likely be requests that need to be performed before a user has signed in such as the account creation request or maybe fetching some form of configuration.

To incorporate this, we can use the OkHttp Tag API, which can also be used in our Retrofit calls. We will tag our web requests with an AuthType enum that specifies which type of authorization to be used.

enum class AuthType {
    ACCESS_TOKEN,
    CLIENT_CREDENTIALS,
    NONE;

    companion object {
        fun fromRequest(request: Request): AuthType =
            request.tag(AuthType::class.java) ?: ACCESS_TOKEN
    }
}

With OkHttp the tag can be specified in the request builder and with Retrofit it can be added using an annotation for a particular call.

interface CreateAccountRemoteApi {
    @POST("user/identity")
    suspend fun createAccount(
        @Body request: CreateAccountRemoteDto,
        @Tag authorization: AuthType = AuthType.CLIENT_CREDENTIALS
    ): AccountCreatedRemoteDto
}

The AuthType can now be extracted and used within our AuthorizationInterceptor and TokenRefreshAuthenticator, to decide how to sign the request.

class AuthorizationInterceptor(
    private val authorizationRepository: AuthorizationRepository
) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val newRequest = chain.request().signedRequest()
        return chain.proceed(newRequest)
    }

    private fun Request.signedRequest() = when (AuthType.fromRequest(this)) {
        AuthType.ACCESS_TOKEN -> signWithFreshAccessToken()
        AuthType.CLIENT_CREDENTIALS -> signWithClientCredentialsToken()
        AuthType.NONE -> this
    }
}

Using this approach we can easily control the exact type of authorization each individual endpoint requires, giving us a great level of flexibility.

Wrap up

Adding authorization to web requests for OkHttp and Retrofit can be done without too much complexity, however, without knowing which parts of the API to use it may not be immediately obvious. By using an Interceptor we can avoid requests failing due to being unauthorized and then we can combine this with an Authenticator to also handle the cases where it does unfortunately fail.

I hope the article was useful. If you have any feedback or questions please feel free to reach out.

Thanks for reading!

Like what you read? Please share the article.

Avatar of Andrew Lord

WRITTEN BY

Andrew Lord

A software developer and tech leader from the UK. Writing articles that focus on all aspects of Android and iOS development using Kotlin and Swift.

Want to read more?

Here are some other articles you may enjoy.