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.

OAuth2 Authorization Grant Flow

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.

Mobile vs. Web Clients

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.

AppAuth

AppAuth for Android and iOS is a client SDK which works with
OAuth2 and OpenID
Connect (OIDC) providers.
It wraps the raw protocol flows into each native platform’s familiar
implementation style.

The SDK follows OAuth 2.0 for Native
Apps best practices,
including the PKCE extension and custom
tab browsers. The library provides hooks to further extend the protocol beyond
the basic flow.

As an open source project, AppAuth has GitHub
repositories for Android and
iOS which include good documentation, a
demo app, and integration with multiple authorization services.

Getting Started

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.

Google OAuth2 and API Registration

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.

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.

Application Architecture

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.

Configuration Discovery

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:

Authorization Code Grant

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:

Code Exchange

If the redirect is successful, the auth repo attempts to exchange the code for
initial access and refresh tokens.

Open and Authorized API Calls

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.

Persistent Authentication State

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.

Finding Favorites

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.

Books App (Android)

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.

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.

Limitations

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.