Photo by Patrick Metzdorf on Unsplash
OAuth2, often combined with OpenID-Connect (OIDC), 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. OAuth2 provides authorization flows for both web and mobile applications.
OAuth2's most popular flow is the authorization code grant flow which is used to authorize confidential clients access to protected resources. Clients use a client id to identify themselves and a client secret to authenticate themselves to the authorization service.
OAuth2 Authorization Code Grant Flow
Authorization code grant has good separation of frontend and backend flows. The frontend flow is delegated to a user agent, typically the system browser, which verifies user credentials and asks the user to grant authorization permissions to the client to access protected resources. Upon success, an authorization code is returned to the client. In the backend flow, the client, authenticated by a client secret, exchanges the authorization code for access and refresh tokens. The client uses an access token to access protected backend resources on behalf of the user.
During the code grant flow, the client secret is only exposed to the authorization server. It is never exposed through the potentially less secure frontend user agent.
In OAuth2, a confidential client is one who can securely protect client secrets. Unfortunately, native apps are considered public, not confidential clients. They are not able to protect static secrets. Obfuscation and code hardening techniques make secrets harder to steal, but not impossible. If the client secret can be stolen, then it can be used by anyone to complete an authorization code exchange.
Since a public client secret is no secret at all, many identity and authorization service providers simply drop the client secret. AppAuth, a popular open source Android and iOS OAuth2 SDK, recommends against utilizing client secrets:
Utilizing client secrets (DANGEROUS)
We strongly recommend you avoid using static client secrets in your native applications whenever possible. Client secrets derived via a dynamic client registration are safe to use, but static client secrets can be easily extracted from your apps and allow others to impersonate your app and steal user data. If client secrets must be used by the OAuth2 provider you are integrating with, we strongly recommend performing the code exchange step on your backend, where the client secret can be kept hidden.
Public clients are open to all kinds of attacks including authorization code and token theft and client impersonation by malicious software. With either an insecure secret or no secret at all, to restore the integrity of the OAuth2 code grant flow for mobile, native app protection must improve from public to confidential client strength.
On a public client using the basic code grant flow, anyone who can observe a frontend authorization code can attempt to exchange it for access and refresh tokens. Proof Key for Code Exchange (PKCE) was added to the basic flow to help offset this weakness. It attempts to ensure that the client initiating the frontend code request is the same client that subsequently request the backend code exchange.
The client first generates a runtime secret called the code_verifier
. In the stringer form of PKCE, the client hashes this secret and sends this code_challenge
value as part of the frontend request. The authorization sever saves this value. With or without a client secret, the client includes the code verifier as part of its subsequent backend code exchange request. The authorization server compares a hash of the code_verifier
with the original code_challenge
it received. If they match, the service will process the code exchange request as usual.
OAuth2 Authorization Code Grant Flow with PKCE
With PKCE, a malicious actor who steals the authorization code cannot successfully complete the code exchange without knowing the original code verifier. The code verifier is a runtime generated secret. As such, it is ephemeral and need not be persisted on the client, so the code verifier can be considered confidential on the mobile client.
A possible attack around PKCE would require generating a fake code_verifier
and injecting the corresponding fake code_challenge
hash into the client’s original frontend request. On observing the returned authorization code, the malicious actor can then send its fake code_verifier
to complete the exchange.
Techniques such as SSL/TLS and certificate pinning can prevent these types of attacks, but they do not prevent a repackaged app from impersonating the app and its authorization flow.
Dynamic client authentication uses remote attestation techniques to verify that an app is genuine and untampered.
A client requests authentication from an app authentication service. The service issues a randomized, attesting challenge, and the client responds with a secure and verifiable response. The server replies with a short-lived approval token which may or may not be valid depending on the attestation outcome. In normal usage, this approval token is added to backend API calls to ensure that only a genuine and approved app can successfully access backend resource services.
API Protection Using Dynamic Client Authentication
With dynamic client authentication, the client has no authentication secret. Instead, the app authentication service and the backend resource services share the app authentication secret. When the app authentication service attests the client app, it signs an approval token, typically a JSON Web Token (JWT), with the authentication secret and returns the signed token to the client. The client adds the token to an API call, and the backend resource service uses the authentication secret to verify that the approval token has not expired and was properly signed by same app authentication secret. Even though the token passes through the client, at no time is the app authentication secret directly exposed to the client.
The app authentication service acts as a source of approval tokens which are, in effect, short-lived, run-time client secrets. With a simple extension to the code grant flow, you can replace a static client_secret
with a sequence of run-time client secrets. Since the app authentication secret is never present in the client, and the approval tokens are cryptographically irreversible, the app can now be considered a confidential OAuth2 client, matching the requirements for a secure authorization code grant flow.
Most importantly, since any tampering with the client app will cause attestation to fail, fake apps can no longer impersonate a valid client during the OAuth2 flow.
A proof of concept implementation, using Android and AppAuth, is demonstrated for the Google Identity Service.
In Adding OAuth2 to Mobile Android and iOS Clients Using the AppAuth SDK, an Android app which searches and finds favorite books was developed demonstrating Google OAuth2, Google APIs, and Android AppAuth libraries. We add dynamic app authentication to the OAuth2 flow using CriticalBlue’s free Approov demo service.
Since we cannot modify Google’s OAuth2 authentication directly, we add an OAuth2 adapter service which can validate Approov’s token and mediate the OAuth2 code for access token exchange.
Secure Mobile OAuth2 Code Grant Flow (PKCE not Shown)
The OAuth2 adapter for Google modifies two authorization endpoints. As a convenience, the OpenID discovery endpoint is modified so that the client can easily configure itself to use the modified authentication service. More significantly, the OAuth2 token endpoint is modified to accept runtime approval tokens from the client.
To follow along, start by cloning the AppAuth-OAuth2-Approov demo project on GitHub available at github.com/approov. The OAuth2 adapter is a node server located in the oauth2-adapter subdirectory of the cloned repository. The Android client app is located in the books-client subdirectory of the cloned repository.
It is important that the client-adapter path be TLS protected and pinned. Since the proof of concept will use a localhost server for demonstration, we’ll pin its self-signed certificate to the Android client and use HTTPS for communication between client, adapter, and Google OAuth2 and API paths.
To generate a TLS public certificate — private key pair, open a terminal and run:
$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem-days 3660 -nodes
Copy both key.pem and cert.pem files into the OAuth2 adapter’s configuration directory located at <your-repo>/oauth2-adapter/config/.
Copy the cert.pem file into the Books app’s assets directory located at <your-repo>/books-client/app/src/main/assets/.
You will be using the Google’s Books API 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
SHA1:C5:A9:B1:F8:A3:8D:07:B3:30:D2:12:06:D2:BA:1E:CF:91:FA:60:97
Ensure the secret.keystore is placed in <your-repo>/books-client/ directory of the cloned repository.
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:
Next, you will create an OAuth2 client ID and secret. You might expect to use the Android application type, but it will not generate a client secret. Instead, make sure you select the Web application type:
Web Application OAuth2 Client Credentials
Complete the OAuth2 consent screen:
Finally, go to Google API Libraries page and find and enable the Google Books API.
An attestation service is used to establish trust between client and proxy server. Open a browser and visit https://www.approov.io/demo-reg.html to get access to the free demo service. Complete the registration, open your email, and unpack the attached zip file into a convenient place.
Configure the OAuth2 adapter to use Approov tokens from the client and the Google client secret with Google.
In the <your-repo>/oauth2-adapter/config directory, copy the secrets_sample.js file into the same directory and name it secrets.js. Set the approov_token_secret value to the string provided in the demo package email .Set the value of the google_client_secret to the client secret provided in the previous Google credentials step.
In the OAuth2 adapter directory, run npm install to complete the adapter set up.
You should now be able to successfully run the adapter using npm start, but by default, Approov enforcement will be disabled.
<your-repo>/oauth2-adapter$ npm start
> [email protected] start <your-repo>/oauth2-adapter> node server.js
CAUTION: Approov token checking is disabled!
Server listening on ports 3000 & 3001
In the books-client directory of your project, create a secret.gradle file which will hold your configuration information:
From the Google credentials step, copy the API key and client ID values into the secrets file.
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.
Next, add the Approov SDK to the Android client. Using the previously unzipped approov demo package, locate the Android approov.aar library, and copy it into the books client’s approov subproject located at <your-repo>/books-client/approov/.
You should now be able to successfully build the Books App. Once it has been built correctly, you can test it out in an Android emulator.
CAUTION: the Google OAuth2 Service for web applications requires an HTTPS redirect URI. Recent Android releases require that thean HTTPS app link verify this link through a hosted assets link file. To avoid extra configuration, it is recommended that the Android device or emulator used for testing run Marshmallow (API 23) or earlier.
To test the set up, start the OAuth2 adapter and then launch the books client in Android. If the API key is properly registered, you should be able to successfully search for books:
To test the OAuth2 service, select the login item in the book client pull down. This should launch an OAuth2 consent screen:
Use any Google user credentials to login, and you should now see a login icon in the client’s top bar, and in the pull down menu, the favorites item should be enabled.
For more usage details and debug, refer to the Adding OAuth2 to Mobile Android and iOS Clients Using the AppAuth SDK article.
To register the books client with Approov, open a terminal or command window and change into the directory inside the unzipped Approov demo archive which holds the proper registration tool for Android and your local environment. On Linux, for example, that directory is <your-demo-dir>/registration-tools/Android/Linux
. Run the registration tool specifying the path to your application APK. For the books client, this would be <your-repo>/books-client/app/build/outputs/apk/app-debug.apk
:
tutorial$ cd <your-demo-dir>/registration-tools/Android/Linux
tutorial$ ./registration -a <your-repo>/books-client/app/build/outputs/apk/app-debug.apk -t ../../registration_access.tok-e 2d
Submitting data…
Success: new app signature added to database.
You can check in the Approov portal that signature ******************************************** has been added to the library ****************
Once the registration has been processed, Approov tokens should start verifying. In the OAuth2 adapter, you can enforce Approov token verification by editing the <your-repo>/oauth2-adapter/config/config.js and setting approov_enforcement
to true. Restart the adapter, and client logins will continue to succeed only as long as the installed app is genuine.
if you rebuild the client app from scratch and reinstall it, logins will fail as the newly installed app is different from the original one. Repeat the registration commands to start approving new logins from the new app.
The proof of concept successfully demonstrates adding dynamic run time secrets to the mobile code grant flow. The enhanced flow is compatible with the original code grant flow, simply requiring the authorization server to validate signed tokens instead of static secrets.
The enhanced secret processing could be easily added to existing authentication services, or, for legacy services, the OAuth2 adapter could be conveniently added as a gateway service within the application resource backend cluster.
While dynamic app authentication is commonly used to protect backend API calls, it is a simple extension to the OAuth2 authorization code grant flow. In addition to all protections accorded a confidential client code grant flow, the app authentication adds client tampering and impersonation protection for all users.
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.