App attestation techniques close common authentication gaps Photo by on Patrick Metzdorf Unsplash Editor's note: This post was originally published in January 2018 and has been revamped and updated for accuracy and comprehensiveness. The latest update was in October 2022. , often combined with (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 OpenID-Connect In this article you will learn how to protect the OAuth2 Authorization Flow and have its client secret dynamically and securely delivered just-in-time to make the API request for the authorization code exchange with the access and ID token. This is achieved with the use of a Mobile App Attestation cloud service that only delivers the secret to genuine and unmodified versions of your mobile app, that are not under attack and running in a trusted environment, as defined by your security policies. On top of this the HTTPS channel will also be protected against MitM attacks by using the Managed Trust Roots features of the same cloud service, which can be updated on the fly without the need to release a new version of your mobile app. Introduction to Some New Concepts The article introduction may overwhelm you with some new technical terms that you may not be very familiar with or haven't heard of yet so we’ll define them here. What is Mobile App Attestation? This is the process of authenticating that a running instance of a mobile app is the exact same one that was uploaded to the app store. This process consists of attesting that the mobile app is not running on a compromised device, hasn’t been modified in any way, isn’t being manipulated during runtime, isn’t a target of an ongoing MitM attack, etc. What are Runtime Secrets? These are secrets provided just-in-time to the mobile app at runtime via secure over-the-air updates from a cloud service, at the moment they are required to make the API requests. Since they are protected by the Mobile App Attestation service on retrieval and subsequent usage in the API calls, the secrets are not delivered if the mobile app is under attack or running in an untrusted environment. What are Managed Trust Roots? The traditional approach to protect the HTTP channel against MitM attacks is to secure it with , but this isn’t viable when the backend API you want to pin is not under your control. In such situations the public key pin can be changed at any time and without any notice, thus breaking your app until you release a new version with updated pins and all of your user base has upgraded to it. certificate pinning To solve this limitation of traditional certificate pinning your app can have access to a set of trusted roots with the same set of certificates authorities typically trusted by the Android and iOS system trusted sites. These trusted roots can be securely updated in your code on the fly without the need to release a new version of your mobile app. What are OAuth2 Flows? 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 asks for the user credentials when not logged-in yet with the OAuth2 provider, and also asks the user to grant authorization permissions for the app to access protected resources. Upon success, an authorization code is returned to the app. In the backend flow, the app is authenticated by a in the requests for exchanging the authorization code for access and refresh tokens. The app then uses the access token to access protected backend resources on behalf of the user. client_secret Mobile OAuth2 Code Grant 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 . 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. This is one part of what makes so difficult. protect static secrets mobile security Since a public client secret is no secret at all, many identity and authorization service providers simply drop the client secret. , a popular open source Android and iOS OAuth2 SDK, : AppAuth recommends against utilizing client secrets Utilizing client secrets (DANGEROUS) We 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. strongly recommend 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. Proof Key for Code Exchange (PKCE) 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. (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 requests the backend code exchange. Proof Key for Code Exchange The client first generates a runtime secret called the code_verifier. In the stronger form of PKCE, the client hashes this secret and sends this code_challenge value as part of the frontend request. The authorization server 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 can prevent these types of attacks, but they cannot eliminate them for good, because certificate pinning can be bypassed on a device that a malicious user or an attacker is in control of, as seen on the article . Using this or other bypass techniques a malicious user or an attacker can login through the app to learn how it uses the backend API to then build a bot to scrape or attack the API to extract data from it or to perform actions that otherwise wouldn’t be possible via the app. Another attack surface to be aware of is when your mobile app is repackaged or cloned with the same OAuth2 credentials, thus being able to impersonate your mobile app and its authorization flow, allowing this way the backend API to be accessed as if the request was from the original app. SSL/TLS and certificate pinning How to Bypass Certificate Pinning with Frida on an Android App Securing the OAuth2 Code Grant Flow To secure the OAuth2 flow we will be using the OAuth2 web flow which requires a instead of the PKCE challenge to protect the authorization code exchange with access and refresh tokens. With a simple extension to the code grant flow, you can replace a static with the Approov Runtime Secret feature to securely deliver the just-in-time of being used. client_secret client_secret client_secret Since the mobile app now receives the just-in-time to use on the authorization code exchange for the access and refresh tokens, and since the is only delivered when the app passes Mobile App Attestation, the app can now be considered a confidential OAuth2 client, matching the requirements for a secure authorization code grant flow. client_secret client_secret Most importantly, since any tampering with the mobile app will cause attestation to fail, fake apps can no longer impersonate a valid client during the OAuth2 flow. To follow along, start by cloning the Books demo project on GitHub available at . It requires some configuration that we will go through in the next steps, so it will not run out of the box. github.com/approov Approov Setup The Approov mobile app attestation cloud service is used to establish trust between the Books App and the Google authentication server and Google books API. To continue following along you need to (no credit card required) and to access the Approov cloud service for registration of runtime secrets and mobile app APKs. register for a free trial install the Approov CLI Enabling the Approov CLI Admin Role The commands you are about to execute require admin privileges, therefore you will need to switch from your developer role to an admin role. First, open a terminal to be able use your Approov CLI, and start by enabling the admin role: eval `approov role admin ` Now that your Approov CLI is ready you can start by enabling the managed trust roots. Enabling Managed Trust Roots for API Requests When making API requests you need to protect the secrets within them from being extracted. Certificate pinning is normally the solution for this, but for third-party APIs you cannot pin against their certificates because you are not in control of when they will be rotated. To solve this, let’s enable managed trust roots for the Approov service and integrate it into the Books App: approov pin -setManagedTrustRoots on This is necessary to block the use of self-signed certificates which are commonly used during a MitM attack, because now any request going through the Approov service will only be able to use official certificates, i.e. only those issued by Certificate Authorities. Next, we need to inform the Approov service about the API domain(s) we want to protect. These are the destination domains of the API requests being made out of your mobile app. For the Books App they are the oauth2.googleapis.com, accounts.google.com, and www.googleapis.com: approov api - add oauth2.googleapis.com -noApproovToken approov api - add accounts.google.com -noApproovToken approov api - add www.googleapis.com -noApproovToken This completes the configuration for protecting the API calls from MitM attacks. If you want to be able to see the Approov Service rejections in the logs of Android Studio, then you need to enable the policy to show the reason for the exception. approov policy -setRejectionReasons on Enabling the Approov Runtime Secrets Protection These secrets are provided to the mobile app at runtime via secure over-the-air updates from the Approov cloud service, just-in-time as required to make API requests. They are protected with Mobile App Attestation on retrieval and subsequent usage in the API calls. When the mobile app starts, an initial mobile app attestation is immediately launched via the SDK provided by Approov. This SDK will call the attestation service, resulting in some challenges for the app to execute and, depending on the results received, the attestation service will decide if it is trustworthy or untrustworthy. Once it is established that the mobile is not running on a compromised device (rooted, jail-broken, etc.), hasn’t been tampered with (re-signed, cloned, modified at runtime, etc.) and is not actively under attack (MitM attack, Instrumentation Framework, etc), then it can receive the runtime secret, because the mobile app has attested successfully. This is the secure delivery method to provide the runtime secrets. To use the Approov Runtime Secrets Protection you need to enable the Approov secure strings feature: approov secstrings -setEnabled With this feature enabled you can remove all the secrets present in your mobile app and get them just-in-time when they are needed at runtime to make the API requests. Google Setup You will be using the to perform open and authorized searches on Android. This requires a OAuth2 authorization screen in order to get the user consent for the Books App to have access to the private portions of the API, such as finding your favorite books. Google’s Books API Google Client Credentials First go to the and sign in. Select or create a new project. Google developer’s console Next, click in on the left menu and then on in the center of the nav bar. Now select the type to create the OAuth2 credentials to use in your project: Credentials + Create Credentials OAuth client ID Now, you might expect to use the Android application type, but it will not generate a client secret. Instead, make sure you select the type: Web application Next, you should be able to fill the form to create your OAuth2 web application credentials: The important part here is to enter an Authorized application URI that you will then set in your file to be then used in the mobile app to intercept the redirection from the Google OAuth2 screen. The URI doesn’t need to be available on your API backend; it just needs to have a valid format, e.g. . local.properties https://example.com/oauth2redirect Now, copy and paste the Web Application OAuth2 Client Credentials into your file as per the file: local.properties /android/local.properties.example oauth2.redirectPath=/oauth2redirect oauth2.redirectHost=example.com oauth2.redirectUri=https://example.com/oauth2redirect oauth2.discoveryEndpoint=https://accounts.google.com/.well-known/openid-configuration google.authorizationScope=openid email profile https://www.googleapis.com/auth/books google.userInfoEndpoint=https://www.googleapis.com/oauth2/v3/userinfo google.clientId=___YOUR_GOOGLE_CLIENT_ID_HERE___ oauth2.redirectScheme=https Oh, but where is the placeholder for the Google client secret? Remember that we will be using the Approov runtime secrets, thus you need to add it with the Approov CLI: approov secstrings -addKey client_secret -predefinedValue ___YOUR_GOOGLE_CLIENT_SECRET_HERE__ _ The gradle build will insert this configuration information into your application as it is building. Remember that your is ignored by git, so neither of these values will be tracked by Git. local.properties Google OAuth2 Screen Now, create the OAuth2 consent screen: Finally, go to page and find and enable the Google Books API. Google API Libraries Proof of Concept Implementation In , the Android example, which searches and finds favorite books, was developed demonstrating , , and libraries. We will now use AppAuth with the OAuth2 web flow that requires a , that will be delivered just-in-time of being used by the Approov Runtime Secrets feature. This makes it possible to securely use the OAuth2 web flow in a mobile app, because the is only delivered to mobile app instances that attest successfully with the cloud service. Adding OAuth2 to Mobile Android and iOS Clients Using the AppAuth SDK Books App Google OAuth2 Google APIs Android AppAuth client_secret client secret Approov Mobile App Attestation 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 or real device, and you should be able to successfully search for books: To test the OAuth2 service, select the login item in the book client pull down (the three vertical dots in the right upper corner). 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 favorite item should be enabled, therefore if you tap on it you should see your favorites: For more usage details and debug, refer to the article. Adding OAuth2 to Mobile Android and iOS Clients Using the AppAuth SDK Setup the Approov Service First, you need to start by adding the Approov service dependency to the mobile app, and for that you need to open the file and add the line to enable jitpack in order to require the dependency from the Git repository on Github: android/build.gradle Next, open the file and add the lines to require the Approov services implementations: android/app/build.gradle In the case of the Books App we need to use two different Approov Services, because the app uses two different HTTP stacks, specifically for the OAuth2 flow and for the Google books API. On your own mobile app you may be using only one HTTP stack, therefore you would need to adjust as required. We support many different ways of adding Approov to your own iOS or Android app, and you can visit our page to see which ones we support, and if you don’t find what you need, please to request support for your use case. HttpsURLConnection OkHttpClient Quickstart Integration contact us Now, copy/paste the Approov Config string from your onboarding email, or alternatively use the Approov CLI to get it: approov sdk -getConfigString Then, open the file and add to it the Approov config string (Note that it is not a secret): src/client/android/local.properties approov.config=#theapproovconfigstring= Even though the Approov config string is not a secret you shouldn’t commit it into your source code. That is why we are adding it here. Now, go back to the file and uncomment the line that loads it into the default config as a resource value: android/app/build.gradle Next, hit that button on Android Studio to synchronize Gradle, and if there are no errors you are ready to start using the Approov services in the Books App. To use Approov, open the class and uncomment just three lines of code to import and initialize the Approov service. You also need to comment out the native so that the one wrapped by Approov is used. When finished your class should look like this: android/app/src/main/java/com/criticalblue/auth/demo/BooksApp.java OkHttpClient BooksApp.java The first Approov Service being initialized wraps the native client and will be used to secure the OAuth2 web flow, provided via the AppAuth SDK, that uses the client under the hood. The Approov service for the client will securely deliver the required just-in-time to perform the authorization code exchange request, but only to mobile apps that pass the Mobile App Attestation challenges from the Approov cloud service. The Approov service also prevents the MitM attacks on the HTTPS communication channel by using the Approov Managed Trust Roots feature to validate the certificates negotiated on the HTTPS handshake, instead of using the device trust store, that can be manipulated as you can see being done in the article . HttpsURLConnection HttpsURLConnection HttpsURLConnection Oauth2 client_secret How to MitM Attack the API of an Android App The second Approov service being initialized wraps the native and will be used to protect the API requests to the Google books API and to download the books images. The reason for using the is that it is the one used, under the hood, by the library to download the book's images. In this context the Approov Service will be used to prevent MitM attacks on the API requests that could be performed to steal the Authorization bearer token from the API request headers, or to just tamper with the returned results or to replace the downloaded images. The Authorization bearer token was the one securely obtained in the OAuth2 authorization code exchange protected with the first Approov service for the client. OkHttpClient OkHttpClient Picasso HttpsURLConnection Using the Approov Service to Secure the OAuth2 Web Flow The Approov Mobile App Attestation service is now ready to be used to protect outgoing requests from being tampered with and to provide runtime secrets just-in-time of being used, but only when the app passes the Mobile App Attestation. The Approov service will be used for the service to protect all outgoing requests against MitM attacks via the Approov Managed Trusted Roots feature to secure API requests to endpoints you are not in control of. When you control the API endpoint we recommend instead the use of Approov Dynamic Certificate Pinning feature. Both features support over-the-air secure updates to the trusted roots and pins respectively. AppAuth SDK HttpsURLConnection You will also switch from using the OAuth2 Mobile Flow to use the more robust and secure OAuth2 Web Flow that uses a runtime delivered securely by Approov just-in-time of being used on the authorization code exchange request. client_secret Time to open the class and to modify it for Approov by commenting and uncomment the lines as instructed in the file, after which it should look like this: android/app/src/main/java/com/criticalblue/auth/demo/auth/AuthRepo.java On the , you have switched from using the OAuth2 mobile flow to use the more secure web flow, and this change also requires other changes in the file, where you need to comment and uncomment the lines as per the instructions in the file comments, leaving you with this: AuthRepo.performCodeExchangeRequest() AndroidManifest.xml Immediately after the function, you can see how the is securely fetched just-in-time to be used for the authorization code exchange request in the function: AuthRepo.performCodeExchangeRequest() client_secret AuthRepo.fetchJustInTimeClientSecret() The function is then invoked from the function each time the user logs in and authorizes the Books App. AuthRepo.fetchJustInTimeClientSecret() AuthRepo.performWebFlowTokenRequest() Using the Approov Service to Secure API Requests to the Google Books API After a user is authenticated with the they can access their favorites, requiring the Authorization bearer token obtained from the OAuth2 authorization code exchange request. This token is protected against a MitM attack by the Approov service for the client used by . AppAuth SDK, HttpsURLConnection AppAuth SDK API requests to the Google Books API will be handled by the that uses the Retrofit package that wraps the ; therefore we will use the Approov service for the to protect these requests from being tampered with in order to extract the Authorization token. If you look into the function, you will see that an is supplied to create a new instance and retrieved from the function that returns an wrapped by Approov, preventing MitM attacks via the Approov Managed Trust Roots feature. BooksRepo OkHttpClient OkHttpClient BookRepo.createBooksAPI() OkHttpClient Retrofit BooksApp.getHttpClient() OkHttpClient Using the Approov Service to Secure the Download of the Books Cover Images The books cover images don’t require any API key or Authorization token to be downloaded, but it’s still good security hygiene to protect their download, otherwise they can be replaced by other images. In a more sophisticated attack the replaced images could contain a payload in them to execute some commands which could download and inject malware on the device. The images are downloaded in the function by invoking function which uses the Picasso image downloader from the function. BookListAdapter.onBindViewHolder() BooksApp.loadImage() BooksApp.getImageDownloader() Running the Books App with Approov Click the run button in Android Studio to build, install and run the Books App , and you should get this screen: in a real device Now, try to login and you should be able to progress through the OAuth2 consent screen and then be presented with this login failure: If you look into the Android Studio logs you will see several entries like this: This occurs because the Approov cloud service is not yet aware of the mobile app release you are using, thus the mobile app fails attestation and the is not provided to be used to exchange the OAuth2 authorization code for the access and refresh token; therefore the login is aborted with a message. To solve this, you need to register each release of the app with Approov, which you will do now. client_secret From the root of this repo execute: approov registration -add android /app/ build /intermediates/ apk /debug/ app-debug.apk For development, this process of registering the app each time you change the code is only necessary when testing that the Approov integration works; therefore you should whitelist your device to always pass the Mobile App Attestation: approov device - add yourdeviceidbase64== - policy default , always -pass, all Read in the Approov docs to learn how to extract it from the logs or from the Approov token. Extracting the Device ID Before you restart the mobile app for the registration to propagate through the Approov cloud service infrastructure. you need to wait around 30 seconds If, after restarting the mobile app, you still don’t reach the OAuth2 screen and the logs still show app-not-registered, then your Android Studio may be saving the APK in another location. Try registering with this command: approov registration -add src /client/ android /app/ build /outputs/ apk /debug/ app-debug.apk Now, you should be able to restart the mobile app and then be able to login. If you rebuild the Books 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. Conclusion This proof of concept successfully demonstrates adding the Approov Runtime Secrets protection to allow the use of the client secret with the more robust web authorization grant flow, instead of the less secure mobile grant flow which doesn’t use a client secret - it only relies on PKCE to secure the authorization code exchange with an access and ID token. It also shows how the Approov Managed Trust Roots feature is used to secure the authorization code grant flow by securing the HTTPS channel against MitM attacks. The enhanced secured web authorization flow is compatible with the mobile native code grant flow, now requiring a client secret, which can be kept secure in the public client. This is achieved through use of the Approov Mobile App Attestation service with its runtime secrets protection and MitM attack prevention features. 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.