Authenticating on Android with the AppAuth Library

Paul Ruiz
Android Developers
Published in
16 min readJan 19, 2022

--

While Google’s own authentication platform for Android does not support WebViews, the Android team recently announced that they are working with multiple identity provider (IDP) partners, including ForgeRock, Microsoft, Okta, and Ping Identity, to move beyond the use of WebViews by embracing and more widely supporting Custom Tabs. Custom Tabs have been an option for some time, and we found it important to put together a guide on how you can update your apps with the AppAuth library (which utilizes Custom Tabs in its implementation) for authenticating and authorizing your Android users.

This article will cover a Proof Key for Code Exchange (PKCE) implementation, the recommended way of using authentication on a mobile device, using the AppAuth library with Google OAuth as the identity provider. By the end of this article you will have learned:

  • how to log a user in through your identity provider
  • how to retrieve profile information for that user through OpenID
  • how to log your user out of your app
  • and how to make API calls for that user after they have been authorized by the IDP.
Example app flow using third-party authorization

While I am using Google for the example IDP, this same logic will also apply to other providers. I will also aim to keep the code snippets as architecturally agnostic as possible, that way you can more easily apply this guide to your own apps with their existing architecture and patterns.

Authentication Flow

Before diving into the code, it’s important to understand how a traditional OAuth 2.0 authentication code flow works. Once you understand the traditional method, we will look at the PKCE implementation that is recommended for mobile development and you’ll see it’s a variation on the traditional authorization code flow.

Authorization code grant flow
  1. The first action a user takes is that they click on a button or are redirected through some other means to an IDP for authentication.
  2. Once the user has logged in and the IDP has confirmed their account, an authorization code is returned back to the user along with a client ID, potentially a client secret, and other optional items.
  3. This authorization code, client ID, and optional secret can be sent to the IDP’s token endpoint to request an access token that will be used for all subsequent API calls. This step may also return a refresh token that can be used to update the access token once it has expired and can no longer be used.
  4. When the user client has the access token, it can be sent to an API as a part of the Authorization header when making a call. If it is still valid and accepted by the API, then the expected data will be returned to the user.
  5. After the access token has expired and can no longer be used with the API, the user can either send their refresh token and client ID back to the IDP’s token endpoint to request a new pair of access and refresh tokens, or they can repeat the authorization flow from the beginning, allowing them to continue using their services.

Now that you know the general idea behind the authentication code flow, it’s time to check out a PKCE flow:

PKCE authentication flow diagram

While this flow is similar, the main difference is that the app will now generate a code verifier and code challenge that will be used to authorize the user rather than directly sending the authorization code to the app. The mobile app will also explicitly request consent from the user to move forward with the authentication process, rather than automatically sending back the authorization code on authentication.

With this schema defined, it’s time to move into the core of this tutorial!

Android App Setup

The first thing you will need to do for this tutorial is set up an Android app. You may already have one ready to go that you plan to work with, and that’s perfectly fine. If you don’t, then please create one now. Your package name will be necessary when setting up Google OAuth in the next section, but for my example I will be using the package name com.ptruiz.authtest.

Once your app is created, there are a few dependencies that you will need to add in your app level build.gradle file for this tutorial. These are a JSON Web Token (JWT) decoder library from Auth0 (this will get explained in more detail later) and the OpenID AppAuth library.

There’s a few other dependencies that I’m using for my app, such as Picasso for image loading, OkHttp for network calls, and Kotlin coroutines for asynchronous operations, but you can ignore those for now as you may have your own preferences or tools already in place.

While still within your build.gradle file, go into the defaultConfig node within the android node and add a manifest placeholder using your app package name.

Once that’s done, you’re all set with your build.gradle file. The next steps will take place in your AndroidManifest.xml file.

As with most apps, you will need to add the INTERNET permission into your manifest, unless you’re a fan of the passive aggressive messaging that we’ve all seen far too many times.

Next you will need to add an intent-filter to an activity that will handle authorization responses. For this example I’m just using my MainActivity, though you may have your own preferred location.

Finally, you will need to add an activity node specifically for the net.openid.appauth.RedirectUriReceiverActivity class. This will have a series of intent-filter nodes that support redirecting back to your app. The scheme used should be a unique identifier specifically for your app, so your app’s package name is a perfect candidate (and required in the case of Google OAuth). The path property is an additional identifier that you can use in case your app listens for multiple types of Intents. The primary reason that this section exists is that Custom Tabs are not programmatically closable by your app, but this Activity can accept a redirect before popping the back stack to remove the Custom Tab from the screen, providing a more seamless experience for your users.

One thing worth noting here is that you won’t be using the logout redirect intent-filter for this tutorial, but I wanted to include it in order to provide a more complete example. While Google OAuth does not support redirecting on logout, other IDPs (such as Ping Identity) do, so I will address the differences later in this tutorial.

At this point your app should be prepped for adding authentication functionality through AppAuth. The next step is going to be setting up Google OAuth, then you’ll return to your Android app to finish the process.

Setting up Google OAuth

Now that you have either a preexisting or brand new Android app, it’s time to set up an identity provider for your project. While Google recommends using the Google sign-in library for our identity provider service, I will go over how to set up Google OAuth as the example IDP without that library in order to show a process that’s similar to how you might use a federated sign-in environment that isn’t from Google. Given that, you’re more than welcome to use another provider that best fits your needs, such as Auth0 or Ping Identity. If you do go with another IDP, feel free to skim over this section to learn how to set up Google OAuth, or simply move on to the next section.

To get started with Google OAuth, you will need to first go to the Google Cloud Platform console. If you have not set up a Google Cloud project before, you will be prompted with a Terms of Service screen that you can review and must agree with in order to continue.

Initial Google Cloud Platform project creation dialog for Terms of Service

At this point you will want to create a new project by going to the top of the screen and clicking on the Select a project dropdown.

Google Cloud Platform’s Select a Project dropdown

Next, select NEW PROJECT from the popup window to be taken to the New Project screen.

Project selection screen showing that no projects are currently available

On this new screen you can either enter in a new project name or use the automatically generated name that is provided for you. Once you’ve selected a project name, click on the blue CREATE button.

Dialog window for creating a new Google Cloud Platform project

You should get a notification once your project is created.

Notification announcing that a Google Cloud Platform project has finished being created

Returning to the top of the page, select the project dropdown and ensure that your project is selected from the dialog window.

Project selection screen showing the currently selected project

Next, go to the navigation bar on the left of the console screen and select APIs & Services -> OAuth consent screen.

On the next screen, click on the radio button next to External and then click on the blue CREATE button. This will bring you to another screen that requests a few items about your app (mostly contact information) that you are required to fill out. Once you’re done entering in the data, click SAVE AND CONTINUE to move to the next screen.

The screen that follows is a bit more complicated. This is where you will enter in the scopes that your app will support. Scopes are a way to limit what users have access to as a way to offer more security. Go ahead and click on the white ADD AND REMOVE SCOPES button to bring up a scopes selector window. You will add the userinfo.email, userinfo.profile, and openid scopes for this tutorial as none of them are considered sensitive.

Once you have selected these three items, click on the UPDATE button to save your choices.

Now you can scroll to the bottom of the page to click on the SAVE AND CONTINUE button. You can continue through the remaining pages in this section.

Finally, back on the main OAuth consent screen, click on the PUBLISH APP button to make your app public for non-predefined testers. If you would prefer to keep your app private for now, then feel free to go back into the app configuration screen to add test email accounts.

Since you haven’t requested any sensitive scopes, this app won’t need to go through a review process to be available. Once you have finished with your OAuth consent screen, select the credentials option in the left navigation pane.

From here you can click on the blue + CREATE CREDENTIALS button at the top of the screen to, as you might have guessed, create some new credentials. For this use-case you will want to select the OAuth client ID option from the dropdown.

The next screen that appears will ask you to identify the type of application you are working on. Select Android to bring up the remaining option fields. You have two more pieces of data that will need to be filled out: your app package name and the SHA-1 certificate fingerprint.

The package name is the one that is associated with your authenticating app, so in my example case it is com.ptruiz.authtest. The SHA-1 gets a little more complicated as the instructions will vary depending on your computer’s operating system. In my case I’m on a Macbook, so I can run the following terminal command to get the SHA-1 from my debug key. You may want to check Stack Overflow for information on how to get the key for your specific machine.

Once you have this information, add it to the Create OAuth client ID screen and click on the blue CREATE button.

After the credentials have finished being created, you should receive a popup dialog that contains your new Client ID. Copy this and store it somewhere as you will need it soon.

At this point you’re ready to start writing some code to take advantage of the Google OAuth API.

Launching an Auth Request

Before diving into our authentication and authorization flow, I want to point out that I have a series of constants saved in my app that I will use throughout this guide.

The main things to note are that the CLIENT_ID should match your own client ID that was provided by your IDP, the redirect URLs should reflect your own package name, and if you’re not using Google OAuth while following along here, then you will also need to update URL_AUTHORIZATION, URL_TOKEN_EXCHANGE, and URL_API_CALL to match your requirements.

With that out of the way, it’s time to look at how you can finally implement authentication. For my implementation I have included four values at the top of my class that will be used throughout the sample. authState stores, as you might guess, the auth state of our app. This includes the ID, access, and refresh tokens. jwt will store identity information for your authenticated user. Finally, authorizationService and authServiceConfig are both used for managing the auth flow in your app.

Now that you have the AuthState object, you will also want to restore it during app startup (such as in onCreate() or in a ViewModel’s init) if the user has been previously authenticated. The recommended way to work with an AuthState object is to serialize it and store it in some form of permanent storage. In this sample case I will use a SharedPreference. If the AuthState object contains an ID token, then you can also initialize your JWT object.

Given that you now have a restoreState() function, you will also want to have another function that persists AuthState.

Along with AuthState, earlier you defined an AuthorizationServiceConfiguration object and an AuthorizationService object. You can initialize the AuthorizationServiceConfiguration object at startup by passing in the various endpoints that you will need for your app.

As for the AuthorizationService, which is the object that will be used for all of your auth calls, you can initialize it by creating a new AppAuthConfiguration object that you then pass into its constructor.

Something interesting to note here is that I’ve added a BrowserAllowList for which Custom Tabs my app will support. While this isn’t necessary, it is an interesting feature to see while setting up your app, and I highly recommend that you look over the options available to you through AppAuthConfiguration.

At this point it’s time to start making our auth request! This is where things get a little more complicated, but it’s definitely manageable. Start by creating a new function, I called mine attemptAuthorization, that you can call when the user hits a login button or performs some other action that requires authorization.

Since you are following a PKCE authentication flow, you will need to create a code verifier and a code challenge to go along with your request. You can start with the code verifier by generating a byte array from a SecureRandom() object, and then encoding it with the Base64 flags URL_SAFE, NO_PADDING, and NO_WRAP to get it ready to send over to Google OAuth.

The code challenge is a little more nuanced, but it’s still not terrible. This is the example that Google’s documentation currently provides:

code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

Sort of helpful, but not perfect, right? What this is saying is that the code_verifier should be hashed using the SHA256 algorithm, and then encoded again to Base64. You can do that with these three lines:

With those two pieces of data ready to go, you can start to create your AuthorizationRequest by using a Builder provided by the AppAuth library. This will need the AuthorizationServiceConfiguration object that you created earlier, your client ID, a response type (for this example you can just use ResponseTypeValues.CODE), your redirect URI, and the code verifier and code challenge that you just created (you could get by with just the code verifier and using the “plain” challenge method when associating the code verifier/challenge with your AuthorizationRequest, but it’s not recommended). This is also where you will associate your scopes with the user.

If you have any other parameters that are required by your IDP, you can add them to this request by creating a new HashMap<String, String> object and adding it to the request builder through a setAdditionalParameters() call.

Once you’re all set, you can call build() on the builder, retrieve an intent from the authorizationService object, and then launch that intent for a Result.

To give you an example of how I launch and receive this Intent, I created a new launcher that fires off the Intent and then listens for the Result.

At this point you should hopefully be able to click on a login button to call attemptAuthorization(), then see both the Google auth screen

and the Drive scope approval screen, as this scope requires the user to provide additional permissions. One thing to note here is that the drive permission that was requested has access to all of the user’s Drive files, so there is an extra warning since the app itself is not verified yet, though that’s about as far as you can get at this point.

In the next section you will learn about the responses received and how to use them to update your app’s auth state.

Handling Authorization Responses and Token Exchange

After your AuthorizationRequest has been received by your IDP, it will be processed, a response will be sent to your app, and that response will be redirected back to your designated Activity attached to an Intent. This response (example shown below) will contain a code that can be exchanged for a token.

com.ptruiz.authtest:/oauth2redirect?state=nZGBhSGUj7r8eCy4y3EGfA
&code=4/0AX4XfWi8xQr5DkoSPSXEKftoY050pLdRn1aKeavmHMjatxlUeaYxTVLPjjXLb4hmHLOZKw
&scope=email%20profile%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/userinfo.email%20openid%20https://www.googleapis.com/auth/drive.file
&authuser=0&prompt=consent

The first thing you’ll do with this response is extract the associated information from it into AuthorizationResponse and AuthorizationException objects. Additionally, since this response contains a state parameter, you can use this to update the AuthState object.

Assuming the authorizationResponse isn’t null, you can use it to create a new TokenExchangeRequest.

This request, when converted to a serialized JSON string, contains various pieces of data that are used throughout the authentication process.

Finally, to send this request you simply need to call the performTokenRequest function on your AuthorizationService, then listen for the response. If no exceptions are raised, then you can update your AuthState and convert a received ID token into a JWT to extract identifying information for the user.

Before moving on, it is worth discussing these responses in a little more detail. This is what the token exchange response looks like when it is received by your app:

What’s particularly interesting here is the id_token property, as it is currently a long string that doesn’t mean much before it’s decoded into a JWT object.

However, once you’re able to decode this token through the Auth0 JWT library that you imported earlier, you get the following JSON that can be used for displaying user profile information. For my sample app I updated a LiveData object based on AuthState, but you can update your UI in whatever way is fitting for your project once the JWT object is available.

Making API Calls

Now that you’re able to authenticate users, it’s time to let them do something. Return to the Google Cloud console and open the API Library section. Once you’re there, search for the Google Drive API, select it, and click on the blue ENABLE button.

While you have already requested the necessary Drive scope, the API still needs to be enabled for you to view and interact with files. Once you’re done enabling that API, return to your app.

You can use the performActionWithFreshTokens call from the AuthState object to make API calls. The nice thing about this method is that it will attempt to automatically refresh your token if it has expired by using the stored refresh token, saving you a bit of time and effort. For a quick example, this is how you could use this method to make a network request after a button has been clicked using coroutines and OkHttp. This request will attempt to retrieve a list of Drive files for the user. One important thing to notice here is that the access token is being used in an Authorization header.

We can see that the response comes back as another JSON object full of data for this mostly empty account, though other APIs are also available from Google and can be interacted with in a similar fashion.

Logging Out

Finally, you will want to provide a way for your user to log out of your app. You may remember from earlier that you added a logout redirect in the AndroidManifest.xml file, though I mentioned that Google OAuth does not support logout redirects. If you happen to be working with an IDP that does support this functionality (for example, Ping Identity), then you can use the EndSessionRequest object from the AppAuth library like so:

All this requires is the access token and logout redirect path because your logout URL was added earlier when setting up the authServiceConfig object.

If you’re using Google OAuth, or any other service that does not support logout redirects, then you can still use their revoke or logout URL while clearing your app state locally.

Wrapping Up

In this tutorial you have learned a bit about OAuth 2’s PKCE authentication flow, as well as how to implement it in your Android apps through the AppAuth library. This tutorial also introduced Google’s OAuth service and how it could be used to access Google APIs, though the same logic can be applied to a wide array of IDPs. Hopefully this will help make the process of adding authentication and authorization to your apps easier going forward so you can make better and more secure apps.

--

--

Paul Ruiz
Android Developers

Developer Programs Engineer on Android, Maker, @ptruiz_dev