Coinbase Logo

Language and region

OkHttp & OAuth: Token Refreshes

By Author

Company

, December 4, 2018

, 2 min read time

Every time we log into an app using our Facebook or Google account, we rely on the authentication protocol OAuth.

As developers, we frequently have to work with APIs that use OAuth as their authentication mechanism. The Coinbase App uses OAuth to authenticate users so they can buy and sell digital currencies with the Coinbase API.

1*RNbHnqRop N4WXIoi7dfZg

“Highly accurate depiction of OAuth authentication”

After receiving a token, apps typically persist it and apply it to each request that requires authentication. In the Coinbase Android app, we do this using an Interceptor. Each request asks an AccessTokenProvider for the token and tacks it onto its headers.

class AccessTokenInterceptor( private val tokenProvider: AccessTokenProvider ) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response { val token = tokenProvider.token()

return if (token == null) { chain.proceed(chain.request()) } else { val authenticatedRequest = chain.request() .newBuilder() .addHeader("Authorization", "Bearer $token") .build() chain.proceed(authenticatedRequest) } } }

Interceptor that signs requests with an access token

/** * Provides an access token for request authorization. */ interface AccessTokenProvider {

/** * Returns an access token. In the event that you don't have a token return null. */ fun token(): String?

/** * Refreshes the token and returns it. This call should be made synchronously. * In the event that the token could not be refreshed return null. */ fun refreshToken(): String? }

Contract for providing and refresh an access token

But what happens when our access token expires or gets revoked? Making a request with an invalid token results in a 401. Wouldn’t it be nice if our network stack could handle refreshing our token automatically and even retry failed requests for callers?

Enter OkHttp’s Authenticator API. Using a custom Authenticator we can build this behavior into OkHttp.

/** * Authenticator that attempts to refresh the client's access token. * In the event that a refresh fails and a new token can't be issued an error * is delivered to the caller. This authenticator blocks all requests while a token * refresh is being performed. In-flight requests that fail with a 401 are * automatically retried. */ class AccessTokenAuthenticator( private val tokenProvider: AccessTokenProvider ) : Authenticator {

override fun authenticate(route: Route?, response: Response): Request? { // We need to have a token in order to refresh it. val token = tokenProvider.token() ?: return null

synchronized(this) { val newToken = tokenProvider.token()

// Check if the request made was previously made as an authenticated request. if (response.request().header("Authorization") != null) {

// If the token has changed since the request was made, use the new token. if (newToken != token) { return response.request() .newBuilder() .removeHeader("Authorization") .addHeader("Authorization", "Bearer $newToken") .build() }

val updatedToken = tokenProvider.refreshToken() ?: return null

// Retry the request with the new token. return response.request() .newBuilder() .removeHeader("Authorization") .addHeader("Authorization", "Bearer $updatedToken") .build() } } return null } }

Custom Authenticator that retries calls and performs token refreshes

Now when building our OkHttpClient we plug our Authenticator in:

OkHttpClient.Builder()

 .authenticator(AccessTokenAuthenticator(accessTokenProvider))

 .build()

Eureka! Instead of having to retry 401s and perform refreshes in our application code, we’ve built this behavior into OkHttp for all our authenticated calls.

Special thanks to 

Tristan Waddington

 for being a sounding board for OAuth and OkHttp internals.

P.S. Coinbase is hiring!

Coinbase logo