PKCE Authorization Code Returns “invalid_grant: Code not valid” When Exchanging Token via Public Client

Hello Keycloak Support Team,

I’m facing an issue with PKCE + Google Identity Provider (OIDC) login flow in Keycloak.
The authorization step succeeds, and I receive the code parameter from the redirect URI, but when I attempt to exchange this code for tokens using the /token endpoint, Keycloak responds with:

{
  "error": "invalid_grant",
  "error_description": "Code not valid"
}

Environment Details

  • Keycloak Version: [specify your version, e.g., 24.0.5]

  • Realm: MyScanApp

  • Client ID: myscan-mobile-v2

  • Client Type: Public

  • PKCE Enforced: true

  • Identity Provider: Google

  • Redirect URIs:

    com.myscan.app:/callback*
    https://oauth.pstmn.io/v1/callback
    
    
  • Flow: Standard Authorization Code + PKCE (S256)

What I’ve Verified

  • PKCE code_verifier and code_challenge are correct (verified SHA256 hash manually).

  • Redirect URI matches exactly in Keycloak and in token exchange request.

  • Code is exchanged within 10–20 seconds of login (so it’s not expired).

  • The client is configured as Public with PKCE Required, Standard Flow Enabled, and Direct Access Grants Enabled.

Example Token Request

POST https://keycloak-dev.bloxbytes.com/realms/MyScanApp/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
client_id=myscan-mobile-v2
redirect_uri=https://oauth.pstmn.io/v1/callback
code=<authorization_code>
code_verifier=<verifier_used_to_generate_challenge>

Expected Result

Keycloak should return a valid token response:

{
  "access_token": "...",
  "refresh_token": "...",
  "id_token": "...",
  "expires_in": 300,
  "token_type": "Bearer"
}

Actual Result

Keycloak returns:

{"error":"invalid_grant","error_description":"Code not valid"}

Could this be related to timing, session binding between the Google broker and the Keycloak authorization code, or a restriction with Postman’s redirect URI (https://oauth.pstmn.io/v1/callback)?
Any guidance on how to debug why Keycloak invalidates the authorization code immediately would be appreciated.