Short answer: OAuth 2.0 is an authorization framework that lets mobile apps obtain limited access to user accounts on an HTTP service. It works by issuing access tokens after user consent, without sharing passwords. For native apps, the Authorization Code with PKCE (Proof Key for Code Exchange) flow is the recommended approach because it prevents interception of the authorization code.
Key takeaways
- Use Authorization Code with PKCE for mobile apps.
- Never hardcode client secrets in mobile apps.
- Store tokens securely using platform keychains.
- Implement token refresh to maintain sessions.
- Validate ID tokens on the client side.
- Always use HTTPS for all OAuth endpoints.
What you will find here
- What is OAuth 2.0 and Why Does It Matter for Mobile?
- The Best OAuth Flow for Mobile Apps: Authorization Code with PKCE
- Token Types You’ll Encounter
- How to Securely Store Tokens on Mobile
- Implementing OAuth in Flutter
- Implementing OAuth in React Native
- Common Security Pitfalls
- Comparison: OAuth Flows in Mobile Apps
- Testing Your OAuth Implementation
- Handling Token Expiration and Refresh
- Conclusion and Next Steps
If you’re building a mobile app that needs to let users log in with their Google, Facebook, or your own backend account, you’ve probably heard of OAuth 2.0. It’s the standard protocol for delegated authorization. But for mobile developers—especially those using Flutter or React Native—implementing OAuth correctly involves more nuance than on the web. Let’s break down what you really need to know.
What is OAuth 2.0 and Why Does It Matter for Mobile?
OAuth 2.0 is not an authentication protocol per se; it’s an authorization framework. It gives your app a token that represents the user’s permission to access specific resources (like their email or calendar) without ever seeing their password. For mobile apps, this is critical because you can’t trust the client environment. Your app runs on a device you don’t control, and hardcoding secrets is a no-go.
The Best OAuth Flow for Mobile Apps: Authorization Code with PKCE
On the web, the Authorization Code flow uses a client secret to exchange the authorization code for tokens. That secret must be kept confidential. In a mobile app, however, you can’t securely store a secret. The device is not a safe place. The Authorization Code with PKCE (Proof Key for Code Exchange) flow solves this by replacing the client secret with a dynamically generated cryptographic key.
Here’s how it works:
- The app generates a random code verifier and its SHA-256 hash (code challenge).
- It sends the user to the authorization server, including the code challenge.
- The user authenticates and consents; the server returns an authorization code.
- The app sends the authorization code and the original code verifier to the token endpoint.
- The server verifies that the code verifier matches the challenge, then issues tokens.
Even if an attacker intercepts the authorization code, they can’t exchange it for tokens without the code verifier. This flow is used by major providers like Google, Apple, and Microsoft.
Token Types You’ll Encounter
In OAuth 2.0, you typically get two tokens: an access token and a refresh token. The access token is short-lived (e.g., one hour) and is used to call your API. The refresh token lives longer and lets you get new access tokens without asking the user to log in again. Store both tokens securely.
How to Securely Store Tokens on Mobile
On iOS, use the Keychain. On Android, use the EncryptedSharedPreferences or the Android Keystore. For cross-platform frameworks like Flutter and React Native, use libraries like flutter_secure_storage or react-native-keychain. Never store tokens in plain text, in SharedPreferences, or in local storage that can be accessed by other apps. Also, consider encrypting tokens with a device-level biometric key for extra protection.
Implementing OAuth in Flutter
In Flutter, the flutter_appauth package is a solid choice. It wraps native AppAuth libraries and supports the PKCE flow out of the box. You configure your authorization endpoint, token endpoint, client ID, and redirect URI. The redirect URI must be a custom scheme (like yourapp://callback) that your app registers to handle.
final result = await FlutterAppAuth().authorize(
AuthorizationTokenRequest(
clientId,
redirectUri,
discoveryUrl: discoveryUrl,
scopes: ['openid', 'profile', 'email'],
),
);
After a successful flow, you get an access token and optionally a refresh token. Then you can attach the access token to API requests as a Bearer token in the Authorization header.
Implementing OAuth in React Native
For React Native, the react-native-app-auth package is the go-to. It also uses AppAuth and supports PKCE. Setup involves defining a config with your issuer, client ID, and redirect URL. Then call authorize to start the flow.
import { authorize } from 'react-native-app-auth';
const config = {
issuer: 'https://accounts.google.com',
clientId: 'YOUR_CLIENT_ID',
redirectUrl: 'com.yourapp://redirect',
scopes: ['openid', 'profile'],
};
const result = await authorize(config);
Again, handle the tokens securely and refresh them when needed.
Common Security Pitfalls
- Using the implicit flow: This flow was designed for SPAs and should not be used in mobile apps because it exposes the access token in the URL.
- Hardcoding client secrets: Any string in your app can be extracted. Use PKCE instead.
- Not verifying the ID token: If you use OpenID Connect, validate the ID token’s signature, issuer, and audience on the client side.
- Poor token storage: Using AsyncStorage instead of secure storage is a common mistake.
For a deeper dive on protecting your APIs, check our guide on How to Secure Mobile App APIs Against Common Threats.
Comparison: OAuth Flows in Mobile Apps
| Flow | Use Case | Security | Recommended? |
|---|---|---|---|
| Authorization Code + PKCE | Mobile & native apps | High (no secret needed) | Yes |
| Authorization Code | Server-side web apps | High (requires secret) | No for mobile |
| Implicit | SPAs (deprecated) | Low (token in URL) | No |
| Client Credentials | Server-to-server | High | Not for user auth |
Testing Your OAuth Implementation
Test with a simple client and the authorization server’s sandbox. Use tools like Postman to simulate requests and inspect token responses. In your app, log the token exchange responses during development to catch errors early. For automated testing, mock the authorization server or use a library like nock for Node.js. Simulate token expiration scenarios to ensure your refresh logic works. Also, test with invalid or revoked tokens to verify your error handling.
Handling Token Expiration and Refresh
Access tokens expire. Your app must handle 401 responses from your API by attempting to refresh the access token using the refresh token. If the refresh fails (e.g., the refresh token is expired or revoked), redirect the user to log in again. Implement a centralized token interceptor in your networking layer. In Flutter, you can use the dio package with an interceptor. In React Native, libraries like axios support interceptors. Don’t forget to synchronize refresh requests to avoid multiple refresh calls when parallel requests fail.
Conclusion and Next Steps
OAuth 2.0 is essential for mobile app authentication, but you must follow the right flow and security practices. Stick with Authorization Code + PKCE, store tokens securely, and always validate tokens. If you’re setting up a CI/CD pipeline for your app, you might also want to read our post on Hello world! to get started with automation. Your users will thank you for a secure and seamless login experience.
Frequently asked questions
What is the difference between OAuth 2.0 and OpenID Connect?
OAuth 2.0 is an authorization framework that provides access tokens to access resources. OpenID Connect is an identity layer on top of OAuth 2.0 that adds an ID token (a JWT) for authentication. In mobile apps, you often use OpenID Connect to verify the user’s identity while OAuth handles API access.
Can I use a client secret in a mobile app?
It’s not recommended. Mobile apps can be decompiled and secrets extracted. The OAuth 2.0 for Native Apps specification advises using PKCE instead of a client secret. PKCE provides equivalent security without requiring a secret.
How do I refresh an access token in a mobile app?
Use the refresh token you received during the initial authorization. Send a POST request to the token endpoint with grant_type=refresh_token, your refresh token, and the client ID. The server returns a new access token and optionally a new refresh token.
What redirect URI should I use for mobile OAuth?
Use a custom URL scheme (e.g., com.yourapp://callback) or a claimed HTTPS URI that your app can intercept. Custom schemes are common but can conflict with other apps. Claimed HTTPS URIs use Universal Links (iOS) or App Links (Android) for more security.
How do I handle logout with OAuth in a mobile app?
Delete the stored tokens locally to log the user out of your app. To also log them out of the authorization server (single sign-out), you can redirect to the server’s logout endpoint with an ID token hint and a post-logout redirect URI. This depends on the provider’s implementation.