Security: OAuth, Tokens & Encryption
📖 Concept
Mobile security is a critical topic for senior Android developers. Google expects you to understand authentication flows, secure key storage, and network security at a deep level.
OAuth 2.0 + PKCE flow for mobile:
1. App generates code_verifier (random) + code_challenge (SHA256 hash)
2. App opens browser with auth URL + code_challenge
3. User authenticates in browser
4. Server redirects back to app with authorization code
5. App exchanges code + code_verifier for access_token + refresh_token
6. App uses access_token for API calls
7. When token expires, use refresh_token to get new access_token
PKCE (Proof Key for Code Exchange) prevents authorization code interception — essential for mobile apps where you can't safely store a client secret.
Token storage:
- Use EncryptedSharedPreferences or Android Keystore for tokens
- NEVER store tokens in plain SharedPreferences or logs
- Clear tokens on logout
Certificate pinning: Prevents man-in-the-middle attacks by verifying the server's certificate matches a known hash. OkHttp supports this natively.
Network security config (Android 9+):
- Cleartext traffic blocked by default
- Custom trust anchors per domain
- Debug-only CA certificates for testing with proxies
💻 Code Example
1// OAuth 2.0 token management2class AuthManager @Inject constructor(3 private val tokenStorage: TokenStorage,4 private val authApi: AuthApi5) {6 suspend fun getValidAccessToken(): String {7 val currentToken = tokenStorage.getAccessToken()8 if (currentToken != null && !isExpired(currentToken)) {9 return currentToken.value10 }11 return refreshToken()12 }1314 private suspend fun refreshToken(): String {15 val refreshToken = tokenStorage.getRefreshToken()16 ?: throw AuthException("No refresh token — re-login required")17 val response = authApi.refreshToken(18 RefreshTokenRequest(refreshToken = refreshToken)19 )20 tokenStorage.saveTokens(response.accessToken, response.refreshToken)21 return response.accessToken22 }23}2425// OkHttp interceptor for automatic token injection + refresh26class AuthInterceptor @Inject constructor(27 private val authManager: AuthManager28) : Interceptor {29 override fun intercept(chain: Interceptor.Chain): Response {30 val token = runBlocking { authManager.getValidAccessToken() }31 val request = chain.request().newBuilder()32 .header("Authorization", "Bearer $token")33 .build()34 val response = chain.proceed(request)3536 if (response.code == 401) {37 response.close()38 // Token expired mid-flight — refresh and retry39 val newToken = runBlocking { authManager.refreshToken() }40 val newRequest = chain.request().newBuilder()41 .header("Authorization", "Bearer $newToken")42 .build()43 return chain.proceed(newRequest)44 }45 return response46 }47}4849// Certificate pinning with OkHttp50val client = OkHttpClient.Builder()51 .certificatePinner(52 CertificatePinner.Builder()53 .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")54 .add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // Backup pin55 .build()56 )57 .build()5859// Encrypted token storage60class TokenStorage @Inject constructor(61 @ApplicationContext context: Context62) {63 private val prefs = EncryptedSharedPreferences.create(64 "secure_prefs",65 MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),66 context,67 EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,68 EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM69 )7071 fun saveTokens(access: String, refresh: String) {72 prefs.edit()73 .putString("access_token", access)74 .putString("refresh_token", refresh)75 .apply()76 }7778 fun getAccessToken(): String? = prefs.getString("access_token", null)79 fun getRefreshToken(): String? = prefs.getString("refresh_token", null)80 fun clearTokens() { prefs.edit().clear().apply() }81}
🏋️ Practice Exercise
Practice:
- Implement OAuth 2.0 + PKCE login flow using AppAuth library
- Create an OkHttp Authenticator that handles token refresh without racing
- Set up certificate pinning with a backup pin
- Implement EncryptedSharedPreferences for secure token storage
- Configure network_security_config.xml for your production and debug environments
⚠️ Common Mistakes
Storing tokens in plain SharedPreferences — easily extractable on rooted devices
Not implementing token refresh — users get logged out when access token expires
Using a single certificate pin without backup — certificate rotation will break your app
Running OAuth flow in a WebView instead of Custom Tabs — WebView can intercept credentials
Logging access tokens — accidental token exposure in logcat/crash reports
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Security: OAuth, Tokens & Encryption. Login to unlock this feature.