Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Retrofit 2 - Authenticator Result

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:

  • https://futurestud.io/blog/retrofit-synchronous-and-asynchronous-requests
like image 745
Advice-Dog Avatar asked Feb 06 '16 08:02

Advice-Dog


People also ask

What is retrofit authenticator?

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.

How would you handle token expiry while making network calls Android?

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.

How do I send a POST request with Basic Auth in retrofit?

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.


3 Answers

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/

like image 85
Advice-Dog Avatar answered Oct 21 '22 04:10

Advice-Dog


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

like image 40
Jari Kokkonen Avatar answered Oct 21 '22 05:10

Jari Kokkonen


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:

  1. preferenceHelper and apiService needs to be provided in some way.
  2. This is not an example that will work for all systems and api's but an example in how adding and refreshing the token should be done using Retrofit 2 Authenticator
like image 24
Alex Avatar answered Oct 21 '22 04:10

Alex