Open utterances-bot opened 4 years ago
Hi Sangsoo
Very detailed article about the topic but I have some questions. what if your updatedAccessToken is somehow returned null from the repository?
For example what happens when some error happens while you are trying to refresh the access token?
@tartarJR In my example, accessTokenRepository
doesn't return null
value. If it fails to get the updated access token, refreshAccessToken
throws an exception. It might be due to a temporal unstable network condition. Authenticator
approach has the auto-retry function for that. If it fails to get the updated access token anyhow, a request will be failed. When there is another request using the access token, refreshAccessToken
will be triggered again since accountRepository
has still an expired access token.
Hi, I'm working on a project which requires Oauth2 authentication. But I don't know why, the Authenticator class doesn't work, everytime I get 401, OkHttp triggers only the Interceptor.
Do you know why? Thanks
What do you do if refresh token is expired? How to cancel request from Authenticator?
@s7aycool As you said, the refresh token can be expired although its expiration time is much longer than access tokens. Or, the refresh token can be invalid when a user revokes the OAuth access. In these cases, we need to send users through an OAuth authorization flow again.
Since the refresh token is not valid anymore, you can't get the new access token successfully. accountRepository.refreshAccessToken()
could either throw an exception or null. At that time, you can take some action. One example could be to notify a user login manger to logout so that users can do OAuth authorization flow again.
Hi , I guess that AccessTokenRepository is the place that run the relevant api that request from server the new access token with the refresh token right ? If yes can you show us the AccessTokenRepository class ?
Thank you for the article! Really, I think it's the most actual and useful information about implementing authentication + refreshing token/secret/whatever according to the new retrofit architecture! Very good notice about side effects when you call many times chain.proceed
in your application interceptor.
So, you should have 2 things - Authenticator to refresh an expired or invalid token; Interceptor to apply the current known token.
Hi @SangsooNam, thank you very much for the article. I have implemented your solution, but I am having some problems resolving the issue of updating an access token when you are running multiple simultaneous requests.
Theif(!accessToken.equals(newAccessToken))
statement does not seem to be enough, and the refresh call is called multiple times. Do you have any suggestion of how can I implement the Authenticator to work with multiple concorrents requests?
@godfather I haven't tested that case deeply but it seems synchronized(this)
doesn't work as expected. The critical section may be not valid for multiple requests. I think synchronizing with an object could help you.
e.g.
private final static Object sLock = new Object();
...
synchronized(sLock) {
...
}
hi @SangsooNam! The article is very helpful and I use it as an example in my code.
I would like to ask you, in case the repository (the okhttp client) needs a header with the refresh token, in order to refreshAccessToken
then how would you add it in the request?
The access token is expired, so in order to refresh it, you need to make a request with the refresh token. How are you supposed to do this?
@gs-ts Typically, with scope
and grant_type
parameters, you first request a token to the backend. The backend returns a response like below.
{
"token_type":"bearer",
"access_token":"{ACCESS_TOKEN_VALUE}",
"expires_in":3600,
"refresh_token":"{REFRESH_TOKEN_VALUE}"
}
As the refresh token
is used to get a renewed access token, you can save it to the persistence layer. Unless this is revoked in the backend, it can be used to refreshAccessToken
.
As I mentioned above, AccessTokenAuthenticator
will be called when the access token is expired. At that time, the logic is to get a new access token by refreshAccessToken
and replace the Authorization
header with that new token. Note that I store the new access token when refreshAccessToken
is called so that I can use it for upcoming requests.
// Need to refresh an access token
final String updatedAccessToken = accountRepository.refreshAccessToken();
return newRequestWithAccessToken(response.request(), updatedAccessToken);
private Request newRequestWithAccessToken(@NonNull Request request, @NonNull String accessToken) {
return request.newBuilder()
.header("Authorization", "Bearer " + accessToken)
.build();
}
Hey, thanks for the great article!
Can you explain the authenticate()
method logic in a bit more detail? Specifically, I don't understand why the access token is refreshed every time when newAccessToken == accessToken
. Wouldn't this mean that the access token gets refreshed constantly?
@lukasz-kalnik-gcx First, authenticate()
is not called every time. That is only called when the request receives HTTP unauthorized error. That time would be the best time to update the token. Note that it's not necessary to update the token if a request doesn't use the token. Most requests would have the access token in the header but some could not. The below logic checks it.
final String accessToken = accountRepository.getAccessToken();
if (!isRequestWithAccessToken(response) || accessToken == null) {
return null;
}
This Authenticator
could be called whenever there is an unauthorized error. In some cases, multiple threads can call the authenticate
method due to failing requests. To avoid multiple updates and race conditions, I use thesynchronized
block.
synchronized (this) {
However, accountRepository.refreshAccessToken()
can be called in any place and the synchronized
block cannot cover those cases. Thus, the below logic checks whether the token was updated within the synchronized
block.
final String newAccessToken = accountRepository.getAccessToken();
// Access token is refreshed in another thread.
if (!accessToken.equals(newAccessToken)) {
return newRequestWithAccessToken(response.request(), newAccessToken);
}
If the token was not updated, it's time to refresh the token.refreshAccessToken()
returns the refreshed token and also changed the stored token so that it will be returned by getAccessToken()
. The refreshed token is used for the new request.
// Need to refresh an access token
final String updatedAccessToken = accountRepository.refreshAccessToken();
return newRequestWithAccessToken(response.request(), updatedAccessToken);
Thank you so much for taking the time and providing a really comprehensive and helpful answer!
My concrete use case is that I have to provide an access token with every API call.
I guess in this case an Interceptor
is better suited to add the authorization header? Because an Authenticator
is called only reactively, that means in my case I would get 401 for every call and then I would have to repeat every call in the Authenticator. So effectively every call would be executed twice, which seems wasteful.
I'm thinking about providing the access token inside an Interceptor
for every call. Then I could use the Authenticator
only for the case when e.g. the access token expires and then it would refresh the access token, store it for use in the Interceptor
and retry the last failed call.
Does it sound plausible to you?
@lukasz-kalnik-gcx Yes, and actually that's the way I'm suggesting in this article.
OkHttpClient client = okHttpClient.newBuilder()
.authenticator(new AccessTokenAuthenticator()) // To update the token when it gets HTTP unauthorized error
.addInterceptor(new AccessTokenInterceptor()) // To set the token in the header.
.build();
As I mentioned in the article, AccessTokenInterceptor
does only set the token in the header.
@Override
public Response intercept(Chain chain) throws IOException {
String accessToken = accountRepository.getAccessToken();
Request request = newRequestWithAccessToken(chain.request(), accessToken);
return chain.proceed(request);
}
Amazing, thanks again for the explanations!
Hi, Thanks for the great article. Can you please share the code of AccessTokenRepository class as well I am unable to find java examples for refreshAccessToken using OkHttpClient
Regards, Priyanka
@PriyankaMadadi85 refreshAccessToken
can be implemented by calling a refresh token endpoint using OkHttpClient. Here is the simplified example.
String refreshAccessToken() throws IOException {
Request request = new Request.Builder()
.url("http://<REFRESH_TOKEN_URL>?refresh_token=" + mRefreshToken)
.build();
try (Response response = mOkHttpClient.newCall(request).execute()) {
String newAccessToken = response.body().string();
setAccessToken(newAccessToken); // For `getAccessToken` later
return newAccessToken;
}
}
I met the same issue as @godfather .
I have implemented your solution, but I am having some problems resolving the issue of updating an access token when you are running multiple simultaneous requests.
Theif(!accessToken.equals(newAccessToken)) statement does not seem to be enough, and the refresh call is called multiple times. Do you have any suggestion of how can I implement the Authenticator to work with multiple concorrents requests?
Sorry, it works. I forgot to update the newAccessToken. Thanks!
// Need to refresh an access token
final String updatedAccessToken = accessTokenRepository.refreshAccessToken();
Is the refreshAccessToken a sync request method? If refreshAccessToken error, is it appropriate to return to the login page here?
@Waylon-Firework In my code, refreshAccessToken
itself is not a synchronous method, and I used synchronized
block within intercept
call in AccessTokenInterceptor
to avoid refreshing it by multiple threads. The code snippet doesn't have that part, but I believe it's possible to redirect a user to the login page by capturing the error.
Hi I have use case where 4-5 api calls firing the auth-interceptor and result in that many refresh token, how does sync block fix this issue?
OkHttp: How to Refresh Access Token Efficiently
When you use the token-based authentication including OAuth, there are two tokens: access token and refresh token. Whenever you need to access a protected re...
http://sangsoonam.github.io/2019/03/06/okhttp-how-to-refresh-access-token-efficiently.html