OAuth2, often combined with OpenID-Connect, is a popular authorization framework that enables applications to protect resources from unauthorized access. It delegates user authentication to an authorization service, which then authorizes third-party applications to access the protected resources on the user’s behalf. OAuth 2 provides authorization flows for both web and mobile applications.
AppAuth is an open source SDK for native Android and iOS apps which implements best-practice OAuth2 and OpenID-Connect specifications in a platform friendly manner.
A sample app, implemented in Android, provides a concrete example using AppAuth to authorize access to private resources. The Books App uses the Google Books API and Google Sign-In services to search for books (protected by API key) and show a signed-in user’s favorite book selections (protected by OAuth2). The open source project is available at github.com/approov.
In OAuth2 Authorization Grant flows, resource authorization is separated from resource access. Only the authorization server needs to handle user credentials, so those user credentials are never exposed to the client or the resource server.
An authorization starts when a client sends a user, termed the resource owner, through redirection, to the authorization server’s website. The local user-agent, usually a browser, obtains and submits the user’s credentials and asks the user to grant permissions. The authorization server validates the credentials and redirects the access token through the user agent and back to the client.
In the Authorization Code Grant flow, authorization is split into two steps. In the first step, if the authorization server authenticates the user credentials, an authorization code is returned to the client. The client calls back to the authorization server with the authorization code and some form of client authentication, usually a client secret. If the client is authenticated, the authorization server returns an access token and optional refresh tokens directly to the client. By separating the authorization process into two steps, the access token does not flow through the user agent.
Access tokens passed from client to resource server can be verified by the resource server using the same secret used to sign them. Both authorization and resource servers share this secret, but this secret is never exposed to the client or user agent. Access tokens have a limited lifetime, so refresh tokens can be used to request fresh access tokens.
To go a little deeper, see Mobile API Security Techniques, Part 2: API Tokens, Oauth2, and Disappearing Secrets
The authorization code grant flow is common for web and mobile clients. A difference between web and mobile flows often shows up during the code exchange step.
Before the authorization server exchanges the code for an access token, it is important that the authorization server ensures that the client is who it claims to be. This is usually done for a web client using HTTP basic authentication with client ID and secret held on the application server.
On a mobile client, that same client secret would be statically held in the native app. Static client secrets are often easy to extract from your apps which allows others to impersonate your app and steal user data. Unfortunately, on mobile clients, it is common to exchange the authorization code for an access token using only the publicly available client ID. Which is better - authenticating using an easily stolen secret or authenticating with no secret at all?
The authorization code is returned to the mobile client by redirection through the user agent. When initially registering the mobile app with the authorization service, the developer may restrict the redirect URLs the authorization service will accept. This helps prevent a malicious actor from redirecting the authorization code to a unrelated URL address.
With no secret required during code exchange, anyone who can intercept an authorization code can exchange the code for an access token. Proof Key for Code Exchange (PKCE) has been adopted by many OAuth2 providers. With plain PKCE, a client app generates a random state value through the initial user agent call to the authorization server. The server saves this value. When the client app performs the code exchange, it sends the original state value along with the code, and the authorization server will not exchange the code for an access token unless the two state values match. The malicious actor must now observe both the initial state value and the access code to grab a token.
In a stronger form of PKCE, the client app sends a hash of the random state value when making the authorization request. During code exchange, it sends the original state value with the code. The authorization server compares a hash of this value with the original hash it received. Now, observing the original authorization request is no longer good enough; the hacker must intercept and modify the initial hash. If successful, the client app will no longer be able to exchange the token, but the attacker will.
PKCE is a good step, but using a client secret, which does not pass through the user agent, would be a safer approach, if it wasn’t so vulnerable when stored statically on a mobile device.
An app which searches and finds favorite books was developed on Android to further explore AppAuth SDK usage with a common application architecture and support libraries.
To follow along, start by cloning the Books demo project on GitHub available at github.com/approov. It requires some configuration, so it will not run out of the box. At a minimum, you will need to provide a keystore, Google API key, and Google OAuth2 credentials which we will generate next.
You will be using Google’s Books API to demonstrate using the AppAuth SDK to perform open and authorized searches on Android. This requires an API key for access to public portions of the API, such as open book search. OAuth2 access tokens are required to access the private portions of the API, such as finding your favorite books.
To register for an API key and OAuth2 credentials for Android, Google requires a public key SHA1 fingerprint, which is usually the fingerprint of the public key which signs your Android application package. For this demo, we’ll create a new secret keystore, and use the same key material for API key, OAuth2 credentials, and your application’s signing configuration.
In a terminal, use the Java keytool to generate a ‘secret’ keystore, and extract the fingerprint. For convenience, you can use ‘secret’ for all parameters.
$ keytool -genkey -keystore secret.keystore -alias secret-keyalg RSA -keysize 2048 -validity 10000 -keypass secret-storepass secret -dname 'CN=secret'
$ keytool -list -v -keystore secret.keystore -storepass secret |grep SHA1
Ensure the secret.keystore is placed in the top directory of your project.
Next go to the Google developer’s console and sign in. Select or create a new project.
The project in this screen shot is shown as ‘Auth Demo’. Create an API key using the secret fingerprint:
Still in the developer’s console, create an OAuth2 client ID:
Complete the OAuth2 consent screen:
Finally, go to Google API Libraries page and find and enable the Google Books API.
In the top-level directory of your project, create a secret.gradle file which will hold your configuration information:
The gradle build will insert this configuration information into your application as it is building. Both secret.keystore and secret.gradle will be ignored by git, so neither of these files will be saved in your repository.
You should now be able to successfully build and try out the Books App. The next few sections describe how AppAuth is used in the application to authenticate the user and to make private Google API calls which require access tokens. After that, public, login, and private use cases are demonstrated in the Books app.
The Books demo app uses a simple MVVM architecture with two activities for searching for books and finding favorites. The favorites activity is only enabled when logged in through the Google OAuth2 sign in service.
The AppAuth Android repository’s demo app shows off many of the AppAuth features, but it mixes UI, AppAuth, and network calls within activities. The Books app separates the AppAuth services into an independent model layer and integrates the authorization services with common libraries such as Retrofit2.
The full OAuth2 authorization code grant flow is separated into individual steps in the AuthRepo class. Long running functions are implemented with Async tasks off the main UI thread. The following sections highlight the major steps. Refer to the application code and the AppAuth libraries for additional detail.
The flow starts with Authorization Service and client configuration. OIDC adds a service discovery capability which looks up and cofigures the service API endpoints and other capabilities. If the discovery endpoint is specified in the secret.gradle file, discovery is tried first. If no configuration is discovered, the service is configured using additional endpoints directly specified in secret.gradle.
The client is configured using values specified in secret.gradle:
The Books app uses a custom tab browser as the user agent, independent of the app itself. AppAuth generates a custom tabs intent which is passed to the search activity which then launches the browser. PKCE is supported transparently within the flow.
The browser launches and asks the user to present authorization credentials and grant permissions.
The browser redirects the authorization server’s response back to the activity which notifies the auth repo to continue:
If the redirect is successful, the auth repo attempts to exchange the code for initial access and refresh tokens.
If authorization is successful, the app can access protected APIs using access tokens. The auth repo provides OKHTTP interceptors to wrap API calls with appropriate keys and access tokens.
The API key interceptor is used for open API calls. The interceptor adds the API key, Android package cert, and package name to each API call as required.
The access token interceptor wraps all protected API calls with a bearer access token. The token is checked and refreshed if necessary before each call.
Immediately after a successful code exchange, the access token interceptor is used to gather user profile information from the Google sign in.
The AppAuth demo app provides an Auth state manager which frequently persists the authentication state into shared preferences. This state survives application restart so an application’s user authentication can persist between app sessions.
The Books app does not persist this state to demonstrate fresh configuration discovery and login each time the app starts. Persistance is a must-have feature in production, and the AppAuth class provides a solid starting point for a robust persistent mechanism.
You might not have any favorite books posted in your Google Books library. In a web browser, sign in to your Google account, go to books.google.com, and click on the My Library link. Browse down to the Favorites bookshelf and add some books by selecting the set up button in the upper right and choosing advanced book search. In the search results, click on a book and add it to favorites in the next screen.
Strictly speaking, read access to your Favorites bookshelf is public, meaning that you can access it with only an API key. There is a catch however; you must first know your Google Books user ID, which is different from your common Google profile ID. To find your Books ID, you must query the API for a list of your bookshelves. This is an authenticated request, and the Google API identifies your Books user ID from your access token. You can parse the user ID out of a successful bookshelves response, and finally you can make a query to your Favorites bookshelf using your access token, an API key, or both.
Below are a few screen shots of the Books app in action. The app launches with no login and an open book search dialog. Open book searches are done using only the API key, with no OAuth authorization required.
The next screen shows some search results. Note that the Favorites are not enabled because no user has logged in.
Picking the login menu item starts the sign in process, launching the custom tab browser.
After entering your credentials, the next screen asks you to accept permissions.
Upon successful authorization, the user icon displays on the top bar. You can now find the favorites of the authorized user.
Though this is a rather limited demonstration, most of the login and use cases are demonstrated including service discovery, independent user agent authorization, and API key and access token API calls. The model and view separation hopefully makes the AppAuth flow relatively easy to follow.
The basic mobile flow, as demonstrated, uses a static client ID but no client secret during code exchange. Though PKCE is used, sign in security is not as robust as the best web client implementations where client ID and secret are used from within the application server.
A follow on article will explore the dynamic registration features of OAuth2 which do not store client secrets statically on the app, but offer limited security during app registration. This can be combined with dynamic client authentication services to implement a secure and full OAUTH2/OIDC authorization code grant flow on mobile devices.
Thanks for reading! For more information on mobile API security, check out www.approov.io.
I’d really appreciate it if you recommend this post (by clicking the 👏 button) so other people can find it.