Retrofit — OAuth on Android

This tutorial in the Retrofit series describes and illustrates how to authenticate against an OAuth API from your Android app.

Let's start with an overview of other tutorials within this series.

Retrofit Series Overview

  1. Introduction to Call Adapters (Coming soon)
  1. Callbacks (Coming soon)
  2. Annotations (Coming soon)
  3. Fluent Interface with Builders (Coming soon)

This post won't go into detail about OAuth itself. It just presents the basic principles and necessary details to understand the authentication flow.

OAuth Basics

OAuth is a token based authorization method which uses an access token for interaction between user and API. OAuth requires several steps and requests against the API to get your access token.

  1. Register an app for the API you want to develop. Use the developer sites of the public API you're going to develop for.
  2. Save client id and client secret in your app.
  3. Request access to user data from your app.
  4. Use the authorization code to get the access token.
  5. Use the access token to interact with the API.

Register Your App

Before starting with the implementation you have to register your app for the service/API you want to develop. Once the sign up for your application (which you're going to build) is finished, you'll receive a client id and a client secret. Both values are required to authenticate your app against the service/API.

Create Your Project

We’ll assume you already have an existing project. If you don’t, just go ahead and create an Android project from scratch. When you’re done, move on to the next section and get ready for coding :)

Integrate OAuth

Since we’re using the ServiceGenerator class from our basic authentication with Retrofit tutorial, we’ll further extend it and add a method to handle the OAuth access token. The snippet below shows the required method within the ServiceGenerator class. That doesn't mean you should delete the previous created method(s) for basic authentication, since you'll need them for OAuth as well.

Retrofit 2

public class ServiceGenerator {

    public static final String API_BASE_URL = "https://your.api-base.url";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create());

    public static <S> S createService(Class<S> serviceClass) {
        return createService(serviceClass, null);
    }

    public static <S> S createService(
            Class<S> serviceClass, String clientId, String clientSecret) {
        if (!TextUtils.isEmpty(clientId)
                && !TextUtils.isEmpty(clientSecret)) {
            String authToken = Credentials.basic(clientId, clientSecret);
            return createService(serviceClass, authToken);
        }

        return createService(serviceClass, null, null);
    }

    public static <S> S createService(
            Class<S> serviceClass, final String authToken) {
        if (!TextUtils.isEmpty(authToken)) {
            AuthenticationInterceptor interceptor =
                    new AuthenticationInterceptor(authToken);

            if (!httpClient.interceptors().contains(interceptor)) {
                httpClient.addInterceptor(interceptor);

                builder.client(httpClient.build());
                retrofit = builder.build();
            }
        }

        return retrofit.create(serviceClass);
    }
}

Retrofit 1.9

public class ServiceGenerator {

    public static final String API_BASE_URL = "https://your.api-base.url";

    private static RestAdapter.Builder builder = new RestAdapter.Builder()
                .setEndpoint(API_BASE_URL)
                .setClient(new OkClient(new OkHttpClient()));

    public static <S> S createService(Class<S> serviceClass) {
        return createService(serviceClass, null);
    }

    public static <S> S createService(Class<S> serviceClass, String username, String password) {
        // we shortened this part, because it’s covered in 
        // the previous post on basic authentication with Retrofit
    }

    public static <S> S createService(Class<S> serviceClass, AccessToken token) {
          if (token != null) {
              builder.setRequestInterceptor(new RequestInterceptor() {
                  @Override
                  public void intercept(RequestFacade request) {
                      request.addHeader("Accept", "application/json")
                      request.addHeader("Authorization", 
                          token.getTokenType() + " " + token.getAccessToken());
                  }
              });
          }

        RestAdapter adapter = builder.build();
        return adapter.create(serviceClass);
    }
}

We're using the Interceptor (RequestInterceptor in Retrofit 1) to set the Authorization field within the HTTP request header. This field consists of two parts: first, the token type which is Bearer for OAuth requests and second, the access token.

As you can see in the code snippet above, the method requires an AccessToken as third parameter. This class looks like this:

public class AccessToken {

    private String accessToken;
    private String tokenType;

    public String getAccessToken() {
        return accessToken;
    }

    public String getTokenType() {
        // OAuth requires uppercase Authorization HTTP header value for token type
        if (! Character.isUpperCase(tokenType.charAt(0))) {
            tokenType = 
                Character
                    .toString(tokenType.charAt(0))
                    .toUpperCase() + tokenType.substring(1);
        }

        return tokenType;
    }
}

The AccessToken class consists of two fields: accessToken and tokenType. Since OAuth API implementations require the token type to be in uppercase, we check the styling first. In case it doesn't fit, we update the style. For example, your API returns bearer as token type, any request with this style would result in either 401 Unauthorized, 403 Forbidden or 400 Bad Request.

The HTTP header field will look like the following example when set correctly:

Authorization: Bearer 12345  

Integrate OAuth in Your App

First, we'll create a new activity called LoginActivity. You can use a simple view with only one button (layout code below). Here's the code for the new activity:

public class LoginActivity extends Activity {

    // you should either define client id and secret as constants or in string resources
    private final String clientId = "your-client-id";
    private final String clientSecret = "your-client-secret";
    private final String redirectUri = "your://redirecturi";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        Button loginButton (Button) findViewById(R.id.loginbutton);
        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse(ServiceGenerator.API_BASE_URL + "/login" + "?client_id=" + clientId + "&redirect_uri=" + redirectUri));
                startActivity(intent);
            }
        });
    }
}

You have to adjust the values for class properties clientId, clientSecret, redirectUri. Also, make sure the partial url for the login is accessible at /login. If not, update this part to the appropriate one. Further, set an onclick listener for the defined login button within the onCreate method. Once the onclick event is fired, it creates a new intent showing a webview for the defined Uri. Important: you have to provide your client id and client secret in this request, since the API requires the two parameters for further operation and processing for the app you're using.

Additionally, check the Uri.parse(…) part. You have to point the url to the login (or authorize) endpoint to show the access rights screen.

The layout for activity_login.xml can look like this.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Login"
        android:id="@+id/loginbutton"
        android:gravity="center_vertical|center_horizontal"
        />
</RelativeLayout>  

It's just a single button on your view :)

Define Activity and Intent Filter in AndroidManifest.Xml

An intent in Android is a messaging object used to request action or information (communication) from another app or component. The intent filter is used to catch a message from an intent, identified by intent's action, category and data.

The intent filter is required to make Android return to your app, so you can grab further data from the response within your intent. That means, when starting the intent after clicking on your login button within your LoginActivity, this filter catches any response and makes additional information available. The code below shows the activity definition in AndroidManifest.xml including the intent filter for this activity.

<activity  
    android:name="com.futurestudio.oauthexample.LoginActivity"
    android:label="@string/app_name"
    android:configChanges="keyboard|orientation|screenSize">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:host="redirecturi"
            android:scheme="your" />
    </intent-filter>
</activity>  

Catch the Authorization Code

Ok, until here we have defined the intent to show the webview which presents as a deny or allow view. You'll notice the style of this view when seeing it.

Now we want to get the access token for further API interaction. This token is another two API requests away. First, we need to parse and use the returned authorization code which is part of the response when pressing the allow button within the intent webview. The following method belongs to your LoginActivity. We separate it since it's easier to explain the contents.

@Override
protected void onResume() {  
    super.onResume();

    // the intent filter defined in AndroidManifest will handle the return from ACTION_VIEW intent
    Uri uri = getIntent().getData();
    if (uri != null && uri.toString().startsWith(redirectUri)) {
        // use the parameter your API exposes for the code (mostly it's "code")
        String code = uri.getQueryParameter("code");
        if (code != null) {
            // get access token
            // we'll do that in a minute
        } else if (uri.getQueryParameter("error") != null) {
            // show an error message here
        }
    }
}

Your app returns into the onResume method of Android's lifecycle. Here you can see we're using the getIntent().getData() methods to retrieve the intent's response.

Now, we don't want to run into any NullPointerException and check the values. Afterwards, we extract the authorization code from query parameters. Imagine the response url when clicking allow like

your://redirecturi?code=1234  

and for deny access like

your://redirecturi?error=message  

Get Your Access Token

You're almost done, the access token is just one request away. Now that we have the authorization code, we need to request the access token by passing client id, client secret and authorization code to the API.

In the following, we just extend the previous presented onResume method to do another API request. But first, we have to extend the LoginService interface and define a method to request the access token. We'll just extend the LoginService from the basic authentication post with another method called getAccessToken.

Retrofit 2

public interface LoginService {  
    @FormUrlEncoded
    @POST("/token")
    Call<AccessToken> getAccessToken(
            @Field("code") String code,
            @Field("grant_type") String grantType);
}

Retrofit 1.9

public interface LoginService {  
    @FormUrlEncoded
    @POST("/token")
    AccessToken getAccessToken(
            @Field("code") String code,
            @Field("grant_type") String grantType);
}

This is the interface definition which is passed to ServiceGenerator to create a Retrofit HTTP client. The getAccessToken method expects two parameters.

Now the complete code for onResume to get the token.

Retrofit 2

@Override
protected void onResume() {  
    super.onResume();

    // the intent filter defined in AndroidManifest will handle the return from ACTION_VIEW intent
    Uri uri = getIntent().getData();
    if (uri != null && uri.toString().startsWith(redirectUri)) {
        // use the parameter your API exposes for the code (mostly it's "code")
        String code = uri.getQueryParameter("code");
        if (code != null) {
            // get access token
            LoginService loginService = 
                ServiceGenerator.createService(LoginService.class, clientId, clientSecret);
            Call<AccessToken> call = loginService.getAccessToken(code, "authorization_code");
            AccessToken accessToken = call.execute().body();
        } else if (uri.getQueryParameter("error") != null) {
            // show an error message here
        }
    }
}

Retrofit 1.9

@Override
protected void onResume() {  
    super.onResume();

    // the intent filter defined in AndroidManifest will handle the return from ACTION_VIEW intent
    Uri uri = getIntent().getData();
    if (uri != null && uri.toString().startsWith(redirectUri)) {
        // use the parameter your API exposes for the code (mostly it's "code")
        String code = uri.getQueryParameter("code");
        if (code != null) {
            // get access token
            LoginService loginService = 
                ServiceGenerator.createService(LoginService.class, clientId, clientSecret);
            AccessToken accessToken = loginService.getAccessToken(code, "authorization_code"); 
        } else if (uri.getQueryParameter("error") != null) {
            // show an error message here
        }
    }
}

And you're done. You probably have to adjust the grant type value for the API you're requesting. The grant type is passed as the second parameter to the getAccessToken(code, grantType) method.

Enjoy authenticating to any OAuth API. If you run into questions or problems, just contact us via @futurstud_io.


Explore the Library

Find interesting tutorials and solutions for your problems.

Miscellaneous