Requesting tokens
curl examples for every supported grant, against the default
demo-client / demo-secret client from a fresh
Quickstart setup.
Password grant
curl -X POST 'http://localhost:8000/token' \
-u 'demo-client:demo-secret' \
-d 'grant_type=password&username=admin&password=admin'
Add &scope=openid to also receive an ID Token: see
Tokens and claims for what comes back.
Under the
oauth21profile this grant is rejected with400and does not appear ingrant_types_supported: OAuth 2.1 removes it entirely.
Client credentials grant
curl -X POST 'http://localhost:8000/token' \
-u 'demo-client:demo-secret' \
-d 'grant_type=client_credentials'
The response carries no refresh_token: the client authenticates itself
on every request, so there is nothing a refresh token could stand in for
(RFC 6749 §4.4.3, "A refresh token SHOULD NOT be included"). Request a
new access token the same way when the current one expires.
Refresh token
curl -X POST 'http://localhost:8000/token' \
-u 'demo-client:demo-secret' \
-d 'grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN'
If the original grant included the openid scope, the refresh re-issues
an ID Token as well. A scope parameter may narrow, but never broaden,
the originally granted scope (RFC 6749 §6). Claims requested via the
OIDC claims parameter persist across the refresh, including a
narrowed one; see
Tokens and claims.
With oauth.refresh_token_rotation: true, each refresh invalidates the
consumed refresh token; reuse revokes the whole rotation family
(RFC 9700 §4.14.2).
Public clients (no secret)
A client registered with token_endpoint_auth_method: none (a CLI,
desktop or native app, SPA, or MCP client - see the public-clients note
in the configuration reference) has no
secret, so it sends no -u: it is identified by client_id in the body,
and the PKCE code_verifier is what proves it started the flow. The
code comes from the browser step (GET /authorize with a
code_challenge, then login), which cannot be scripted with curl; only
the exchange is shown here.
curl -X POST 'http://localhost:8000/token' \
-d 'grant_type=authorization_code' \
-d 'code=CODE_FROM_THE_AUTHORIZE_REDIRECT' \
-d 'client_id=cli-client' \
-d 'redirect_uri=http://127.0.0.1:8765/callback' \
-d 'code_verifier=YOUR_PKCE_CODE_VERIFIER'
A public client MUST use PKCE with S256 (the request is rejected
without it), cannot use the client_credentials grant, and always gets a
rotating refresh token. A client_secret sent by a public client is
ignored.
Resource indicators (RFC 8707)
To bind the access token to a specific resource server - so a token for
MCP server A is rejected by server B - send a resource parameter (an
absolute URI without a fragment). An MCP client sends it on both
/authorize and /token; the access token's aud becomes that
resource. resource is repeatable for more than one target, and a
/token request may narrow the resources an authorization code bound but
never widen them. A resource outside the client's allowed_resources (or
not a valid URI) is rejected with invalid_target.
curl -X POST 'http://localhost:8000/token' \
-u 'demo-client:demo-secret' \
-d 'grant_type=client_credentials' \
-d 'resource=https://mcp.example/server'
# the returned access token's aud is https://mcp.example/server
Sending no resource leaves the audience at oauth.audience, unchanged.
Device authorization flow
# 1. Request device code
curl -X POST 'http://localhost:8000/device_authorization' \
-u 'demo-client:demo-secret' \
-d 'scope=openid'
# Response:
# {
# "device_code": "...",
# "user_code": "ABCD1234",
# "verification_uri": "http://localhost:8000/device",
# "expires_in": 600
# }
# 2. User visits verification_uri and enters user_code
# 3. Poll for token
curl -X POST 'http://localhost:8000/token' \
-u 'demo-client:demo-secret' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=YOUR_DEVICE_CODE'
UserInfo
The email / profile claims (email, email_verified,
preferred_username, ...) are returned here, using the access token - not
embedded in the ID Token. See
Tokens and claims
for the details and for the claims request parameter that can put a
claim inside the ID Token.
curl 'http://localhost:8000/userinfo' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Token introspection
curl -X POST 'http://localhost:8000/introspect' \
-u 'demo-client:demo-secret' \
-d 'token=YOUR_ACCESS_TOKEN'
Token revocation
curl -X POST 'http://localhost:8000/revoke' \
-u 'demo-client:demo-secret' \
-d 'token=YOUR_ACCESS_TOKEN'