OAuth 2.0 and OpenID Connect

OAuth 2.0 and OpenID Connect

Modern mobile apps often use OAuth 2.0 and OpenID Connect for secure authentication with external providers.

// Android - OAuth 2.0 implementation with PKCE
import net.openid.appauth.*
import java.security.MessageDigest
import java.security.SecureRandom

class OAuthManager(private val context: Context) {
    
    companion object {
        private const val REDIRECT_URI = "com.app.oauth://callback"
        private const val CLIENT_ID = "your_client_id"
        private const val AUTHORIZATION_ENDPOINT = "https://auth.example.com/authorize"
        private const val TOKEN_ENDPOINT = "https://auth.example.com/token"
    }
    
    private val authService = AuthorizationService(context)
    private val secureStorage = SecurePreferencesManager(context)
    
    // Initiate OAuth flow with PKCE
    fun startAuthentication(activity: Activity) {
        val serviceConfig = AuthorizationServiceConfiguration(
            Uri.parse(AUTHORIZATION_ENDPOINT),
            Uri.parse(TOKEN_ENDPOINT)
        )
        
        // Generate PKCE parameters
        val codeVerifier = generateCodeVerifier()
        val codeChallenge = generateCodeChallenge(codeVerifier)
        
        // Store code verifier securely
        secureStorage.saveString("pkce_verifier", codeVerifier)
        
        // Build authorization request
        val authRequest = AuthorizationRequest.Builder(
            serviceConfig,
            CLIENT_ID,
            ResponseTypeValues.CODE,
            Uri.parse(REDIRECT_URI)
        )
            .setScopes("openid", "profile", "email", "offline_access")
            .setCodeVerifier(codeVerifier, codeChallenge, "S256")
            .setPrompt("consent")
            .setAdditionalParameters(mapOf(
                "access_type" to "offline",
                "include_granted_scopes" to "true"
            ))
            .build()
        
        // Create intent
        val authIntent = authService.getAuthorizationRequestIntent(authRequest)
        activity.startActivityForResult(authIntent, AUTH_REQUEST_CODE)
    }
    
    // Handle OAuth callback
    fun handleAuthorizationResponse(
        intent: Intent,
        onSuccess: (TokenResponse) -> Unit,
        onError: (AuthorizationException) -> Unit
    ) {
        val response = AuthorizationResponse.fromIntent(intent)
        val exception = AuthorizationException.fromIntent(intent)
        
        if (response != null) {
            // Exchange authorization code for tokens
            exchangeCodeForTokens(response, onSuccess, onError)
        } else {
            onError(exception ?: AuthorizationException.GeneralErrors.PROGRAM_CANCELED_AUTH_FLOW)
        }
    }
    
    private fun exchangeCodeForTokens(
        authResponse: AuthorizationResponse,
        onSuccess: (TokenResponse) -> Unit,
        onError: (AuthorizationException) -> Unit
    ) {
        val codeVerifier = secureStorage.getString("pkce_verifier")
        
        val tokenRequest = authResponse.createTokenExchangeRequest(
            mapOf("code_verifier" to codeVerifier)
        )
        
        authService.performTokenRequest(tokenRequest) { tokenResponse, exception ->
            if (tokenResponse != null) {
                // Store tokens securely
                storeTokenResponse(tokenResponse)
                
                // Clear PKCE verifier
                secureStorage.remove("pkce_verifier")
                
                onSuccess(tokenResponse)
            } else {
                onError(exception ?: AuthorizationException.GeneralErrors.SERVER_ERROR)
            }
        }
    }
    
    // Refresh access token
    fun refreshAccessToken(
        onSuccess: (String) -> Unit,
        onError: (AuthorizationException) -> Unit
    ) {
        val refreshToken = secureStorage.getString("refresh_token")
        
        if (refreshToken.isNullOrEmpty()) {
            onError(AuthorizationException.GeneralErrors.USER_CANCELED_AUTH_FLOW)
            return
        }
        
        val serviceConfig = AuthorizationServiceConfiguration(
            Uri.parse(AUTHORIZATION_ENDPOINT),
            Uri.parse(TOKEN_ENDPOINT)
        )
        
        val tokenRequest = TokenRequest.Builder(
            serviceConfig,
            CLIENT_ID
        )
            .setGrantType(GrantTypeValues.REFRESH_TOKEN)
            .setRefreshToken(refreshToken)
            .build()
        
        authService.performTokenRequest(tokenRequest) { tokenResponse, exception ->
            if (tokenResponse != null) {
                storeTokenResponse(tokenResponse)
                onSuccess(tokenResponse.accessToken!!)
            } else {
                onError(exception ?: AuthorizationException.GeneralErrors.SERVER_ERROR)
            }
        }
    }
    
    private fun generateCodeVerifier(): String {
        val bytes = ByteArray(32)
        SecureRandom().nextBytes(bytes)
        return Base64.encodeToString(
            bytes,
            Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
        )
    }
    
    private fun generateCodeChallenge(codeVerifier: String): String {
        val digest = MessageDigest.getInstance("SHA-256")
        val hash = digest.digest(codeVerifier.toByteArray())
        return Base64.encodeToString(
            hash,
            Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
        )
    }
    
    private fun storeTokenResponse(tokenResponse: TokenResponse) {
        tokenResponse.accessToken?.let {
            secureStorage.saveString("access_token", it)
        }
        
        tokenResponse.refreshToken?.let {
            secureStorage.saveString("refresh_token", it)
        }
        
        tokenResponse.idToken?.let {
            secureStorage.saveString("id_token", it)
        }
        
        tokenResponse.accessTokenExpirationTime?.let {
            secureStorage.saveLong("token_expiry", it)
        }
    }
}