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)
}
}
}