I’m stuck on the final leg of the OAuth flow for a custom Android app using Kotlin. The goal is to authenticate users via the Genesys Cloud Web Messaging SDK without storing client secrets. I’ve implemented the Authorization Code flow with PKCE as per the docs. The initial redirect to https://api.mypurecloud.com/oauth/authorize works, and I get the code parameter back in the redirect_uri.
The issue is when I try to exchange that code for an access token via POST https://api.mypurecloud.com/oauth/token. The request keeps returning a 400 Bad Request. I’ve triple-checked the code_verifier matches the hashed code_challenge, but something is still off.
Here’s the Kotlin code handling the token request:
val formData = listOf(
Pair("grant_type", "authorization_code"),
Pair("code", authCode),
Pair("redirect_uri", "myapp://callback"),
Pair("client_id", clientId),
Pair("code_verifier", codeVerifier)
)
val body = formData.joinToString("&") { "${it.first}=${URLEncoder.encode(it.second, "UTF-8")}" }
val request = Request.Builder()
.url("https://api.mypurecloud.com/oauth/token")
.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), body))
.build()
The response body is just: {"error":"invalid_grant","error_description":"The authorization code has expired or is invalid."}. I’m generating the code_verifier using SecureRandom and base64url encoding the SHA-256 hash for the challenge. I’m not sure if the Android HttpURLConnection or OkHttp client is mangling the code_verifier string during URL encoding, or if the timing is just too tight. The code is used within seconds of receiving it. I’ve tried printing the raw code_verifier string to the logs, and it looks like a valid 43-character string. No special characters that should break the encoding. Is there a specific requirement for the code_challenge_method header I’m missing? The docs imply it defaults to S256, but I haven’t explicitly set it.