Skip to Content
MuoCeon Docs
Third-party OAuth IntegrationAuthorization flows

Exchanging and refreshing tokens

The three grant types at the token endpoint, client authentication, and refresh rotation semantics.

Token endpoint: POST {issuer}/token, with an application/x-www-form-urlencoded body. Responses carry Cache-Control: no-store.

Client authentication

MethodApplies toHow
client_secret_basicConfidential clients (recommended)Authorization: Basic base64(client_id:client_secret)
client_secret_postConfidential clientsclient_id and client_secret in the form body
nonePublic clients (web_client / desktop / ios_macos / android)Only client_id in the form body

The authorization endpoint never accepts client_secret

Authorization requests to the consent UI accept only client_id. A secret placed in a browser URL or form is treated as an invalid request. Confidential clients must keep "obtaining the code" in the frontend and "exchanging the code" in the backend.

Grant type 1: authorization_code

ParameterRequiredNotes
grant_type✅authorization_code
code✅The authorization code from the callback
redirect_uri✅Exactly the same as in the authorization request (loopback ports still follow the registration rules)
code_verifier✅The raw PKCE value; it must derive the code_challenge sent earlier
client_id✅ (with none)Required for public clients

The server's validation order: client authentication → code exists and unused → redirect_uri match → PKCE → scope filtering.

On success it returns an access token and a refresh token (the refresh token only when the scope includes offline_access), plus an id_token when openid was requested.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "scope": "openid profile email offline_access",
  "id_token": "eyJ..."
}

expires_in is determined by the redirect platform: 3600 seconds for web_server, 1800 seconds for public client platforms.

Grant type 2: refresh_token

curl -X POST 'https://id.example.com/v1/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d 'grant_type=refresh_token' \
  -d "refresh_token=$REFRESH_TOKEN"

Rules:

  • scope is optional and may only narrow: it must be a subset of the originally granted scopes; anything outside is rejected.
  • Every refresh rotates: you get a new access token and a new refresh token, and the old refresh token stops working immediately. Overwrite your stored value — do not keep the old one for a "fallback retry".
  • Refresh tokens are signed JWTs, but clients must not parse their contents or assume their length or format is stable.
  • Refresh does not restore authorization_details: fine-grained grants hold only for the original authorization and are not carried in refreshed tokens.

Refresh tokens expire too

30 days for confidential clients, 14 days for public clients, and refreshing never extends beyond that cap. After expiry the user must authorize again.

Grant type 3: client_credentials

Available only to machine clients, for server-to-server calls, with no user involved.

curl -X POST 'https://id.example.com/v1/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d 'grant_type=client_credentials' \
  -d 'scope=auth:sessions:read'
  • Issued tokens have no sub; the principal claim distinguishes "represents a user" from "represents an application".
  • offline_access is not allowed: with no user there is no session to refresh, so requesting that scope yields invalid_scope.
  • Third-party applications do not need this grant type; if you do, the product shape should be registered as machine.

Token exchange

First-party admin consoles can use RFC 8693 to obtain tokens for other audiences; third parties do not need it. See Token exchange.

Error response format

The token, revocation and introspection endpoints follow RFC 6749 §5.2, not the unified envelope used by the console APIs:

{
  "error": "invalid_grant",
  "error_description": "The authorization code has already been used"
}
errorHTTPMeaning
invalid_request400Missing parameters, duplicate parameters, unsupported grant_type
invalid_client401Client authentication failed (wrong secret, wrong method)
invalid_grant400Code/refresh token invalid, used or expired; PKCE mismatch; redirect_uri mismatch
unauthorized_client400The client may not use this grant type
invalid_scope400Invalid scope, or an attempt to widen scopes during refresh
server_error500Server-side failure

Rate limiting returns 429 with the unified envelope and a Retry-After header; see Error codes.

On this page