Last active
October 30, 2023 15:14
-
-
Save naturalwarren/bc7f64f003a0e6034c6e74a2aa8917f6 to your computer and use it in GitHub Desktop.
An OkHttp Authenticator that performs token refreshes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 | |
} | |
} |
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?
@cyph3rcod3r That's what the synchronized is for I think.
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
@RahulSDeshpande it didn't work for me until I added
.removeHeader("Authorization")
before.addHeader("Authorization", ....
atAuthenticator
class,Interceptor
class is used to add Authorization header (atInterceptor.intercept()
func) andAuthenticator.authenticate()
fun is only called by OkHttp to refresh access token when it's 401