Skip to content

Instantly share code, notes, and snippets.

@naturalwarren
Last active October 30, 2023 15:14
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save naturalwarren/bc7f64f003a0e6034c6e74a2aa8917f6 to your computer and use it in GitHub Desktop.
Save naturalwarren/bc7f64f003a0e6034c6e74a2aa8917f6 to your computer and use it in GitHub Desktop.
An OkHttp Authenticator that performs token refreshes.
/**
* 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
}
}
@davidbilik
Copy link

Small note - you don't have to call remove/add on headers, just call .header("Authorization", "Bearer $updatedToken") and it will replace the previous one :)

@RahulSDeshpande
Copy link

@davidbilik Good point.

@naturalwarren Thanks for the snippet :)

@anonym24
Copy link

anonym24 commented Sep 29, 2022

@RahulSDeshpande it didn't work for me until I added .removeHeader("Authorization") before .addHeader("Authorization", .... at Authenticator class, Interceptor class is used to add Authorization header (at Interceptor.intercept() func) and Authenticator.authenticate() fun is only called by OkHttp to refresh access token when it's 401

@cyph3rcod3r
Copy link

What if I am doing multiple parallel calls and the first api throws and unauthorised error with this approach.

  • Will it not cause duplicity of refresh call?
  • Will all the other calls wait and be re executed after this?

@ridcully99
Copy link

@cyph3rcod3r That's what the synchronized is for I think.

@jamessavery
Copy link

jamessavery commented Oct 29, 2023

I find that in the time it takes the new token API call to complete, the response would have proceeded with a null token and failed.

How is this working for anyone? My usecase is that in event of a 401 we make an api call to get the new token (using the refreshToken) then

EDIT: I've found a solution. I basically stall till the refresh token call completes & is set:

private fun retryNewToken(oldToken: String?): String? {
   while (oldToken == sessionManager.getAuthToken()) {
       // Keep looping till call completes
   }

   if (sessionManager.getAuthToken().isNullOrEmpty()) return null

   return sessionManager.getAuthToken()
}

For this approach to work, its imperative that failed token refreshes sets the token to null in sessionManager

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment