We are using Retrofit in our Android app, to communicate with an OAuth2 secured server. Everything works great, we use the RequestInterceptor to include the access token with each call. However there will be times, when the access token will expire, and the token needs to be refreshed. When the token expires, the next call will return with an Unauthorized HTTP code, so that's easy to monitor. We could modify each Retrofit call the following way: In the failure callback, check for the error code, if it equals Unauthorized, refresh the OAuth token, then repeat the Retrofit call. However, for this, all calls should be modified, which is not an easily maintainable, and good solution. Is there a way to do this without modifying all Retrofit calls?
-
1This looks relevant to my other question. I will look into it again soon, but one possible approach is wrapping OkHttpClient. Something like this: github.com/pakerfeldt/signpost-retrofit Also, since I'm using RoboSpice with Retrofit, creating a base Request class may be another possible approach as well. Probably you'll have to figure out how to achieve your flow without a Context though, maybe using Otto/EventBus.Hassan Ibraheem– Hassan Ibraheem2014-03-21 20:39:46 +00:00Commented Mar 21, 2014 at 20:39
-
1Well you could fork it, and remove the unneeded cases. I will look into this maybe today, and post here if I achieved something that might solve our problem.Daniel Zolnai– Daniel Zolnai2014-03-22 11:12:47 +00:00Commented Mar 22, 2014 at 11:12
-
2Turned out, that the library didn't handle refreshing tokens, but gave me an idea. I made a small gist about some !untested code, but in theory, I think it should work: gist.github.com/ZolnaiDani/9710849Daniel Zolnai– Daniel Zolnai2014-03-22 17:20:40 +00:00Commented Mar 22, 2014 at 17:20
-
3@neworld A solution I can think of: make the changeTokenInRequest(...) synchronized, and at the first line, check when was the last time the token was refreshed. If it has been just some seconds (milliseconds) ago, do not refresh the token. You can also set this timeframe to 1 hour or so, to stop constantly requesting new tokens when there's another problem outside the token being outdated.Daniel Zolnai– Daniel Zolnai2014-06-09 11:54:25 +00:00Commented Jun 9, 2014 at 11:54
-
2Retrofit 1.9.0 just added support for OkHttp 2.2, which has interceptors. This should make your job a lot easier. For more info, see: github.com/square/retrofit/blob/master/… and github.com/square/okhttp/wiki/Interceptors You have to extend OkHttp for these too, though.Daniel Zolnai– Daniel Zolnai2015-01-12 20:13:36 +00:00Commented Jan 12, 2015 at 20:13
11 Answers
Please do not use Interceptors to deal with authentication.
Currently, the best approach to handle authentication is to use the new Authenticator API, designed specifically for this purpose.
OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.
public class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Attach an Authenticator to an OkHttpClient the same way you do with Interceptors
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Use this client when creating your Retrofit RestAdapter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
35 Comments
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I broke this cycle? Two different OkHttpClients? They are going to have different connection pools...If you are using Retrofit >= 1.9.0 then you could make use of OkHttp's new Interceptor, which was introduced in OkHttp 2.2.0. You would want to use an Application Interceptor, which permits you to retry and make multiple calls.
Your Interceptor could look something like this pseudocode:
public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// close previous response
response.close()
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder()...build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
}
After you define your Interceptor, create an OkHttpClient and add the interceptor as an Application Interceptor.
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new CustomInterceptor());
And finally, use this OkHttpClient when creating your RestAdapter.
RestService restService = new RestAdapter().Builder
...
.setClient(new OkClient(okHttpClient))
.create(RestService.class);
Warning: As Jesse Wilson (from Square) mentions here, this is a dangerous amount of power.
With that being said, I definitely think this is the best way to handle something like this now. If you have any questions please don't hesitate to ask in a comment.
6 Comments
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..
If you have, say, a Retrofit TokenService that you need inside your Authenticator but you would only like to set up one OkHttpClient you can use a TokenServiceHolder as a dependency for TokenAuthenticator. You would have to maintain a reference to it at the application (singleton) level. This is easy if you are using Dagger 2, otherwise just create class field inside your Application.
In TokenAuthenticator.java
public class TokenAuthenticator implements Authenticator {
private final TokenServiceHolder tokenServiceHolder;
public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
this.tokenServiceHolder = tokenServiceHolder;
}
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
//is there a TokenService?
TokenService service = tokenServiceHolder.get();
if (service == null) {
//there is no way to answer the challenge
//so return null according to Retrofit's convention
return null;
}
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken().execute();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
In TokenServiceHolder.java:
public class TokenServiceHolder {
TokenService tokenService = null;
@Nullable
public TokenService get() {
return tokenService;
}
public void set(TokenService tokenService) {
this.tokenService = tokenService;
}
}
Client setup:
//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(tokenAuthenticator);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient)
.build();
TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);
If you are using Dagger 2 or a similar dependency injection framework there are some examples in the answers to this question
5 Comments
TokenService class created?refreshToken() from service.refreshToken().execute();. Not able to find it's implementation anywhere.TokenService directly instead of TokenServiceHolder?Using TokenAuthenticator like @theblang answer is a correct way for handle refresh_token.
Here is my implement (I have using Kotlin, Dagger, RX but you may use this idea for implement to your case)
TokenAuthenticator
class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {
override fun authenticate(route: Route, response: Response): Request? {
val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
return response.request().newBuilder()
.header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
.build()
}
}
For prevent dependency cycle like @Brais Gabin comment, I create 2 interface like
interface PotoNoneAuthApi { // NONE authentication API
@POST("/login")
fun login(@Body request: LoginRequest): Single<AccessToken>
@POST("refresh_token")
@FormUrlEncoded
fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}
and
interface PotoAuthApi { // Authentication API
@GET("api/images")
fun getImage(): Single<GetImageResponse>
}
AccessTokenWrapper class
class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
private var accessToken: AccessToken? = null
// get accessToken from cache or from SharePreference
fun getAccessToken(): AccessToken? {
if (accessToken == null) {
accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
}
return accessToken
}
// save accessToken to SharePreference
fun saveAccessToken(accessToken: AccessToken) {
this.accessToken = accessToken
sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
}
}
AccessToken class
data class AccessToken(
@Expose
var token: String,
@Expose
var refreshToken: String)
My Interceptor
class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val authorisedRequestBuilder = originalRequest.newBuilder()
.addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
.header("Accept", "application/json")
return chain.proceed(authorisedRequestBuilder.build())
}
}
Finally, add Interceptor and Authenticator to your OKHttpClient when create service PotoAuthApi
Demo
https://github.com/PhanVanLinh/AndroidMVPKotlin
Note
Authenticator flow- Example API
getImage()return 401 error code authenticatemethod insideTokenAuthenticatorwill fired- Synchronize
noneAuthAPI.refreshToken(...)called - After
noneAuthAPI.refreshToken(...)response -> new token will add to header getImage()will AUTO called with new header (HttpLoggingWILL NOT log this call) (interceptinsideAuthInterceptorWILL NOT CALLED)If
getImage()still failed with error 401,authenticatemethod insideTokenAuthenticatorwill fired AGAIN and AGAIN then it will throw error about call method many time(java.net.ProtocolException: Too many follow-up requests). You can prevent it by count response. Example, if youreturn nullinauthenticateafter 3 times retry,getImage()will finish andreturn response 401If
getImage()response success => we will result the result normally (like you callgetImage()with no error)
Hope it help
2 Comments
As Brais Gabin said in the comment I had the problem that TokenAuthenticator depends on a service class. The service class depends on an OkHttpClient instance and to create an OkHttpClient I need the TokenAuthenticator.
So how I broke this cycle?
I created a new okHttpClient object, a new Retrofit object and with that object I did the call to get the new token using the refreshToken ( check getUpdatedToken() function)
class TokenAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
// 1. Refresh your access_token using a synchronous api request
val response = getUpdatedToken(refreshToken)
//2. In my case here I store the new token and refreshToken into SharedPreferences
response.request.newBuilder()
.header("Authorization", "Bearer ${tokenResponse.data?.accessToken}")
.build()
// 3. If there's any kind of error I return null
}
}
private suspend fun getUpdatedToken( refreshToken: String): TokenResponse {
val okHttpClient = OkHttpClient().newBuilder()
.addInterceptor(errorResponseInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service = retrofit.create(RefreshTokenApi::class.java)
return service.refreshToken(refreshToken)
}
}
RefreshTokenApi
interface RefreshTokenApi {
@FormUrlEncoded
@POST("refreshToken")
suspend fun refreshToken(
@Field("refresh_token") refreshToeken: String
): TokenResponse
}
In this project I'm using Koin and I configured this way:
object RetrofigConfig {
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
fun provideOkHttpClient(
tokenAuthenticator: TokenAuthenticator
): OkHttpClient {
return OkHttpClient().newBuilder()
.authenticator(tokenAuthenticator)
.build()
}
fun provideServiceApi(retrofit: Retrofit): ServiceApi {
return retrofit.create(ServiceApi::class.java)
}
}
The important line there is OkHttpClient().newBuilder().authenticator(tokenAuthenticator)
Because this is the first time I'm implementing this I don't know if this is the best way but is the way it is working in my project.
3 Comments
runBlocking or is there just for simplicity?authenticate function, right?I know this an old thread, but just in case someone stumbled in it.
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..
I was facing the same problem, but I wanted to create only one OkHttpClient becuase I don't think that I need another one for just the TokenAuthenticator itself, I was using Dagger2, so I ended up providing the service class as Lazy injected in the TokenAuthenticator, you can read more about Lazy injection in dagger 2 here, but it's like basically saying to Dagger to NOT go and create the service needed by the TokenAuthenticator right away.
You can refer to this SO thread for sample code: How to resolve a circular dependency while still using Dagger2?
Comments
Using one Interceptor (inject the token) and one Authenticator (refresh operations) do the job but:
I had a double call problem too: the first call always returned a 401: the token wasn't inject at the first call (interceptor) and the authenticator was called: two requests were made.
The fix was just to reaffect the request to the build in the Interceptor:
BEFORE:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request();
//...
request.newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
AFTER:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request();
//...
request = request.newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
IN ONE BLOCK:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request().newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
Hope it helps.
Edit: I didn't find a way to avoid the first call to always returning 401 using only the authenticator and no interceptor
Comments
You can try creating a base class for all your loaders in which you would be able to catch a particular exception and then act as you need. Make all your different loaders extend from the base class in order to spread the behaviour.
9 Comments
After Long research, I customized Apache client to handle Refreshing AccessToken For Retrofit In which you send access token as parameter.
Initiate your Adapter with cookie Persistent Client
restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_END_POINT)
.setClient(new CookiePersistingClient())
.setLogLevel(RestAdapter.LogLevel.FULL).build();
Cookie Persistent client which maintains cookies for all requests and checks with each request response, if it is unauthorized access ERROR_CODE = 401, refresh access token and recall the request, else just processes request.
private static class CookiePersistingClient extends ApacheClient {
private static final int HTTPS_PORT = 443;
private static final int SOCKET_TIMEOUT = 300000;
private static final int CONNECTION_TIMEOUT = 300000;
public CookiePersistingClient() {
super(createDefaultClient());
}
private static HttpClient createDefaultClient() {
// Registering https clients.
SSLSocketFactory sf = null;
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params,
CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", sf, HTTPS_PORT));
// More customization (https / timeouts etc) can go here...
ClientConnectionManager cm = new ThreadSafeClientConnManager(
params, registry);
DefaultHttpClient client = new DefaultHttpClient(cm, params);
// Set the default cookie store
client.setCookieStore(COOKIE_STORE);
return client;
}
@Override
protected HttpResponse execute(final HttpClient client,
final HttpUriRequest request) throws IOException {
// Set the http context's cookie storage
BasicHttpContext mHttpContext = new BasicHttpContext();
mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
return client.execute(request, mHttpContext);
}
@Override
public Response execute(final Request request) throws IOException {
Response response = super.execute(request);
if (response.getStatus() == 401) {
// Retrofit Callback to handle AccessToken
Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() {
@SuppressWarnings("deprecation")
@Override
public void success(
AccessTockenResponse loginEntityResponse,
Response response) {
try {
String accessToken = loginEntityResponse
.getAccessToken();
TypedOutput body = request.getBody();
ByteArrayOutputStream byte1 = new ByteArrayOutputStream();
body.writeTo(byte1);
String s = byte1.toString();
FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput();
String[] pairs = s.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
if (URLDecoder.decode(pair.substring(0, idx))
.equals("access_token")) {
output.addField("access_token",
accessToken);
} else {
output.addField(URLDecoder.decode(
pair.substring(0, idx), "UTF-8"),
URLDecoder.decode(
pair.substring(idx + 1),
"UTF-8"));
}
}
execute(new Request(request.getMethod(),
request.getUrl(), request.getHeaders(),
output));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void failure(RetrofitError error) {
// Handle Error while refreshing access_token
}
};
// Call Your retrofit method to refresh ACCESS_TOKEN
refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback);
}
return response;
}
}
3 Comments
here is my code working for me. may be helpful for some one
class AuthenticationInterceptorRefreshToken @Inject
constructor( var hIltModules: HIltModules,) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
if (response.code == 401) {
synchronized(this) {
val originalRequest = chain.request()
val authenticationRequest = originalRequest.newBuilder()
.addHeader("refreshtoken", " $refreshToken")
.build()
val initialResponse = chain.proceed(authenticationRequest)
when (initialResponse.code) {
401 -> {
val responseNewTokenLoginModel = runBlocking {
hIltModules.provideAPIService().refreshToken()
}
when (responseNewTokenLoginModel.statusCode) {
200 -> {
refreshToken = responseNewTokenLoginModel.refreshToken
access_token = responseNewTokenLoginModel.accessToken
val newAuthenticationRequest = originalRequest.newBuilder()
.header("refreshtoken",
" $refreshToken")
.build()
return chain.proceed(newAuthenticationRequest)
}
else -> {
return null!!
}
}
}
else -> return initialResponse
}
}
}; return response
}
Comments
To anyone who wanted to solve concurrent/parallel calls when refreshing token. Here's a workaround
class TokenAuthenticator: Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
response?.let {
if (response.code() == 401) {
while (true) {
if (!isRefreshing) {
val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)
currentToken?.let {
if (requestToken != currentToken) {
return generateRequest(response, currentToken)
}
}
val token = refreshToken()
token?.let {
return generateRequest(response, token)
}
}
}
}
}
return null
}
private fun generateRequest(response: Response, token: String): Request? {
return response.request().newBuilder()
.header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
.header(AuthorisationInterceptor.AUTHORISATION, token)
.build()
}
private fun refreshToken(): String? {
synchronized(TokenAuthenticator::class.java) {
UserService.instance.token?.let {
isRefreshing = true
val call = ApiHelper.refreshToken()
val token = call.execute().body()
UserService.instance.setToken(token, false)
isRefreshing = false
return OkHttpUtil.headerBuilder(token)
}
}
return null
}
companion object {
var isRefreshing = false
}
}