DEV Community

myougaTheAxo
myougaTheAxo

Posted on

OAuth2/PKCE on Android: Authorization Flow & Token Management

OAuth2/PKCE on Android: Authorization Flow & Token Management

OAuth2 with PKCE (Proof Key for Code Exchange) is the secure standard for mobile authentication. It protects against authorization code interception attacks.

PKCE Code Challenge Generation

class OAuth2Manager(private val clientId: String) {
    fun generateCodeChallenge(): Pair<String, String> {
        val codeVerifier = generateRandomString(128)
        val bytes = codeVerifier.toByteArray()
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(bytes)
        val codeChallenge = Base64.getUrlEncoder().withoutPadding().encodeToString(digest)
        return codeVerifier to codeChallenge
    }

    private fun generateRandomString(length: Int): String {
        val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
        return (1..length).map { chars.random() }.joinToString("")
    }
}
Enter fullscreen mode Exit fullscreen mode

Authorization Request

fun requestAuthorizationCode(codeChallenge: String) {
    val authUrl = Uri.Builder()
        .scheme("https")
        .authority("oauth.example.com")
        .appendPath("authorize")
        .appendQueryParameter("client_id", clientId)
        .appendQueryParameter("redirect_uri", redirectUri)
        .appendQueryParameter("response_type", "code")
        .appendQueryParameter("code_challenge", codeChallenge)
        .appendQueryParameter("code_challenge_method", "S256")
        .build()

    val intent = Intent(Intent.ACTION_VIEW, authUrl)
    startActivity(intent)
}
Enter fullscreen mode Exit fullscreen mode

Token Exchange with Code Verifier

suspend fun exchangeCodeForToken(
    authCode: String,
    codeVerifier: String
): TokenResponse = withContext(Dispatchers.IO) {
    val body = FormBody.Builder()
        .add("grant_type", "authorization_code")
        .add("code", authCode)
        .add("client_id", clientId)
        .add("code_verifier", codeVerifier)
        .add("redirect_uri", redirectUri)
        .build()

    val request = Request.Builder()
        .url("https://oauth.example.com/token")
        .post(body)
        .build()

    httpClient.newCall(request).execute().use { response ->
        Json.decodeFromString(response.body!!.string())
    }
}
Enter fullscreen mode Exit fullscreen mode

Secure Token Storage

class SecureTokenStorage(private val context: Context) {
    private val sharedPrefs = EncryptedSharedPreferences.create(
        context, "oauth_tokens", context.mainEncryptedSharedPrefs(),
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    fun saveToken(token: TokenResponse) {
        sharedPrefs.edit().putString("access_token", token.accessToken).apply()
    }

    fun getAccessToken(): String? = sharedPrefs.getString("access_token", null)
}
Enter fullscreen mode Exit fullscreen mode

PKCE provides protection against interception attacks on mobile platforms. Always use encrypted storage for tokens and implement token refresh flows.


8 Android app templates available on Gumroad

Top comments (0)