I'm trying to use Retrofit (2.0.0-beta3), but when using an Authenticator to add a token, I can't seem to get the data from the synchronous call. Our logging on the back-end just shows a lot of login attempts, but I can't get the data from the body to actually add to the header.
public static class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
UserService userService = createService(UserService.class);
Call<Session> call = userService.emailLogin(new Credentials("handle", "pass"));
// This call is made correctly, as it shows up on the back-end.
Session body = call.execute().body();
// This line is never hit.
Logger.d("Session token: " + body.token);
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header("Auth-Token", body.token)
.build();
}
}
I'm not exactly too sure on why it's not even printing anything out. Any tips on how to solve this issue would be greatly appreciated, thanks for taking the time to help.
These are the sources I've been reading on how to implement Retrofit.
Using Authenticator:
https://stackoverflow.com/a/31624433/3106174
https://github.com/square/okhttp/wiki/Recipes#handling-authentication
Making synchronous calls with Retrofit 2:
Retrofit is a type-safe HTTP client by Square that was built for the Android platform. It offers an easy and clean way to make REST API network calls and parses the JSON/XML response(s) into Java Objects which we can then use in our app.
If you want to handle the token expiry issue with Fuel, you can save the expiry time also in the shared preferences in addition to the token and token type. Then once an api call request fails with a 401 response code, you can assume that one reason may be the token has expired.
Approach. You will have to create a Request interceptor ( BasicAuthInterceptor ) which extends Interceptor class of OkHttp library. Then, override intercept function and add your credentials into the request. Generate basic credentials with Credentials class of package OkHttp by using its basic function.
I managed to get a decent solution using the TokenAuthenticator and an Interceptor and thought I'd share the idea as it may help some others.
Adding the 'TokenInterceptor' class that handles adding the token to the header is the token exists, and the 'TokenAuthenticator' class handles the case when there is no token, and we need to generate one.
I'm sure there are some better ways to implement this, but it's a good starting point I think.
public static class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate( Route route, Response response) throws IOException {
...
Session body = call.execute().body();
Logger.d("Session token: " + body.token);
// Storing the token somewhere.
session.token = body.token;
...
}
private static class TokenInterceptor implements Interceptor {
@Override
public Response intercept( Chain chain ) throws IOException {
Request originalRequest = chain.request();
// Nothing to add to intercepted request if:
// a) Authorization value is empty because user is not logged in yet
// b) There is already a header with updated Authorization value
if (authorizationTokenIsEmpty() || alreadyHasAuthorizationHeader(originalRequest)) {
return chain.proceed(originalRequest);
}
// Add authorization header with updated authorization value to intercepted request
Request authorisedRequest = originalRequest.newBuilder()
.header("Auth-Token", session.token )
.build();
return chain.proceed(authorisedRequest);
}
}
Source:
http://lgvalle.xyz/2015/07/27/okhttp-authentication/
I have similar authenticator and it works with 2.0.0-beta2.
If you get lots of login attempts from you Authenticator, I suggest make sure that when you make the synchronous call, you are not using Authenticator with that call. That could end up in loop, if also your "emailLogin" fails.
Also I would recommend adding loggingInterceptor to see all trafic to server: Logging with Retrofit 2
I know it's a late answer but for anyone still wondering how to Add / Refresh token with Retrofit 2 Authenticator, here is a working solution:
Note: preferenceHelper is your Preference Manager class where you set/get your shared preferences.
public class AuthenticationHelper implements Authenticator {
private static final String HEADER_AUTHORIZATION = "Authorization";
private static final int REFRESH_TOKEN_FAIL = 403;
private Context context;
AuthenticationHelper(@ApplicationContext Context context) {
this.context = context;
}
@Override
public Request authenticate(@NonNull Route route, @NonNull Response response) throws IOException {
// We need to have a token in order to refresh it.
String token = preferencesHelper.getAccessToken();
if (token == null)
return null;
synchronized (this) {
String newToken = preferencesHelper.getAccessToken();
if (newToken == null)
return null;
// Check if the request made was previously made as an authenticated request.
if (response.request().header(HEADER_AUTHORIZATION) != null) {
// If the token has changed since the request was made, use the new token.
if (!newToken.equals(token)) {
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + newToken)
.build();
}
JsonObject refreshObject = new JsonObject();
refreshObject.addProperty("refreshToken", preferencesHelper.getRefreshToken());
retrofit2.Response<UserToken> tokenResponse = apiService.refreshToken(refreshObject).execute();
if (tokenResponse.isSuccessful()) {
UserToken userToken = tokenResponse.body();
if (userToken == null)
return null;
preferencesHelper.saveAccessToken(userToken.getToken());
preferencesHelper.saveRefreshToken(userToken.getRefreshToken());
// Retry the request with the new token.
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + userToken.getToken())
.build();
} else {
if (tokenResponse.code() == REFRESH_TOKEN_FAIL) {
logoutUser();
}
}
}
}
return null;
}
private void logoutUser() {
// logout user
}
}
Also note:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With