Skip to Content
MuoCeon Docs
Integration and debugging

Token exchange and refresh

Investigation order for invalid_grant, why refresh tokens stop working, why scopes go missing, and whether to validate with jwks or introspect.

invalid_grant when exchanging the code

Check in order:

CheckNotes
Was the code already usedCodes are single-use; reuse always fails
Has it expiredCode lifetimes are short; exchange immediately after receiving one
Do code_verifier and code_challenge pair upThe PKCE S256 result must match
Does redirect_uri match verbatimIt must equal the value in the authorization request exactly
Is client authentication correctConfidential clients must use their registered credentials

The full list is in Error codes.

Why did my refresh token suddenly stop working

Two causes:

  • Rotation — every refresh issues a new value and kills the old one immediately. If two code paths refresh with the same old value, one must fail;
  • Expiry — refresh tokens themselves expire (30 days for confidential clients, 14 days for public clients).

Serialise refreshes in concurrent scenarios: allow only one in-flight refresh per account per client.

Why is a scope I requested missing from the response

The server filters scopes by client capability, and filtering is a silent drop with no error. Trust the scope in the response, not what you requested. See Scopes and audiences.

Why does UserInfo return 401 with my access token

Most often the audience is wrong: UserInfo only accepts auth audience tokens whose scope includes at least one of openid / profile / email. The other common cause is an expired or revoked token.

Validate with jwks or introspect

MethodSuitable forCost
Local validation with jwksHigh call volume, a 5-minute cache is acceptableMust handle kid rotation and clock skew
introspectNeeds real-time revocation stateConfidential clients only; a server round trip every time

Public clients can only validate locally.

On this page