OAuth Token Storage Security: Best Practices for Web & Mobile Apps
Securely handling OAuth access and refresh tokens is essential for protecting user data in web and mobile applications. Follow best‑practice storage methods to reduce token theft risk.
Focus: OAuth token storage security
OAuth Token Storage Security: Best Practices for Web and Mobile Apps
Overview
OAuth 2.0 is the de‑facto standard for delegated access. After a user authorizes an app, the server issues an access token (short‑lived) and often a refresh token (long‑lived). If these tokens are compromised, an attacker can act on behalf of the user. The core challenge is to store them in a way that minimizes exposure while keeping the user experience smooth.
Web Applications
1. Store Access Tokens in Memory or Short‑Lived Cookies
- In‑memory: Keep the access token in a JavaScript variable or a server‑side session. It disappears when the session ends, reducing the window for theft.
- HttpOnly, Secure cookies: If you must persist the token, use a cookie flagged
HttpOnly(inaccessible to JavaScript) andSecure(sent only over HTTPS). This mitigates XSS attacks.
# Example: Setting a secure cookie in Flask
response = make_response()
response.set_cookie(
key="access_token",
value=access_token,
httponly=True,
secure=True,
samesite="Strict",
max_age=3600 # 1 hour
)2. Store Refresh Tokens Securely on the Server
- Never expose refresh tokens to the client. Keep them in a server‑side database, linked to the user’s session. When the access token expires, the server silently requests a new one using the stored refresh token.
3. Use the Authorization Code Flow with PKCE
- For public clients (e.g., single‑page apps), employ the Authorization Code Flow with PKCE. PKCE adds a cryptographic challenge that prevents token replay attacks even if the authorization code is intercepted.
4. Token Refresh Strategy
- Refresh tokens should have a reasonable lifetime (e.g., 30 days) and be revocable. Implement a revocation endpoint so users can invalidate tokens if they suspect compromise.
Mobile Applications
1. Use Platform‑Specific Secure Storage
- iOS: Store tokens in the Keychain. It is encrypted, sandboxed, and protected by the device’s passcode.
- Android: Use EncryptedSharedPreferences or the Android Keystore to keep tokens encrypted at rest.
# Example: Using Android Keystore (pseudo‑code)
from android.keystore import KeyStore
keystore = KeyStore.getInstance("AndroidKeyStore")
keystore.load(null) # no password for this example
# store token as a secret entry2. Prefer Short‑Lived Access Tokens
- Mobile apps often have limited storage and higher risk of device theft. Issue access tokens that expire quickly (e.g., 5–15 minutes) and rely on refresh tokens for renewal.
3. Implement Silent Token Refresh
- Use a background service that detects token expiration and triggers a silent refresh using the stored refresh token. This avoids prompting the user repeatedly.
4. Guard Against Root/Jailbreak
- Detect rooted or jailbroken devices. If detected, refuse to store tokens or store them in a more restricted environment (e.g., memory only).
Common Pitfalls
- Storing tokens in localStorage or sessionStorage: These storages are accessible to any script running on the page, making them vulnerable to XSS.
- Hard‑coding tokens in source code: Tokens embedded in the app binary can be extracted by reverse engineering.
- Using the same token across multiple clients: A compromised token on one platform exposes the user on all platforms.
- Neglecting token revocation: Without a way to invalidate tokens, stolen tokens remain usable until they naturally expire.
Practical Example: Secure Token Flow (Web)
# 1. User initiates login
auth_url = f"{AUTH_ENDPOINT}?response_type=code&client_id=MY_CLIENT&redirect_uri={REDIRECT_URI}&code_challenge={pkce_challenge}&code_challenge_method=S256"
# 2. After redirect, exchange code for tokens
import requests
def exchange_code_for_tokens(code):
payload = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
"client_id": CLIENT_ID,
"code_verifier": pkce_verifier # PKCE verifier
}
resp = requests.post(TOKEN_ENDPOINT, data=payload)
data = resp.json()
return data["access_token"], data["refresh_token"]
access_token, refresh_token = exchange_code_for_tokens(auth_code)
# 3. Store access token in an HttpOnly cookie (server side)
# Store refresh token in a server‑side DB linked to the user sessionBest‑Practice Checklist
- ✅ Use HTTPS everywhere.
- ✅ Keep access tokens short‑lived.
- ✅ Store refresh tokens only on the server or in platform‑secure storage.
- ✅ Apply HttpOnly, Secure, SameSite flags for cookies.
- ✅ Implement PKCE for public clients.
- ✅ Provide a revocation endpoint.
- ✅ Regularly audit token handling code for XSS and injection risks.
Summary
Secure token storage hinges on separating concerns: keep short‑lived access tokens out of persistent storage, protect refresh tokens with server‑side mechanisms or platform‑specific secure storage, and always use TLS. By following the guidelines above — short token lifetimes, HttpOnly cookies, PKCE, and secure mobile storage — developers can dramatically reduce the risk of token theft in both web and mobile applications.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.