OAuth 2.1 Token Endpoint
Token Endpoint requirements specific to OAuth 2.1 (beyond OpenID Connect).
Endpoint Requirements
HTTP Method
- MUST use POST method
- Other methods not allowed
Content Type
- MUST accept
application/x-www-form-urlencoded - UTF-8 character encoding
TLS
- MUST use HTTPS
- Except localhost for development
CORS Support
For browser-based apps:
- Token endpoint MUST support CORS headers
- Return appropriate
Access-Control-Allow-Origin
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.example.com
Content-Type: application/json
Supported Grant Types
authorization_code
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
refresh_token
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
client_credentials
POST /token HTTP/1.1
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
Token Request Processing
Client Authentication
def process_token_request(request):
# 1. Authenticate confidential clients
if client.is_confidential:
if not authenticate_client(request):
return error_response("invalid_client")
# 2. Validate grant type specific parameters
# 3. Issue tokens
Parameter Handling
- MUST ignore unrecognized parameters
- Parameters without value treated as omitted
- Parameters MUST NOT be included more than once
Token Response
Success Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "openid profile"
}
Required Fields
| Field | Description |
|-------|-------------|
| access_token | REQUIRED. The access token |
| token_type | REQUIRED. Type of token (e.g., "Bearer") |
Optional Fields
| Field | Description |
|-------|-------------|
| expires_in | RECOMMENDED. Lifetime in seconds |
| refresh_token | OPTIONAL. Refresh token |
| scope | RECOMMENDED if same as request, REQUIRED if different |
Token Type
- Value is case-insensitive
Bearer,bearer,BEARERare all valid
Required Headers
Cache-Control
Cache-Control: no-store
MUST include in any response containing tokens.
Pragma (Legacy)
Pragma: no-cache
May include for legacy HTTP/1.0 compatibility.
Error Response
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "invalid_grant",
"error_description": "Authorization code expired",
"error_uri": "https://example.com/docs/errors#invalid_grant"
}
Error Codes
| Error | Description |
|-------|-------------|
| invalid_request | Missing/invalid parameter |
| invalid_client | Client authentication failed (401 status) |
| invalid_grant | Invalid grant (code, refresh_token, etc.) |
| unauthorized_client | Client not authorized for grant type |
| unsupported_grant_type | Grant type not supported |
| invalid_scope | Invalid/unknown scope |
Error Field Character Restrictions
error: %x20-21 / %x23-5B / %x5D-7Eerror_description: %x20-21 / %x23-5B / %x5D-7Eerror_uri: URI-reference syntax
Authorization Code Grant Specifics
Request Parameters
| Parameter | Requirement |
|-----------|-------------|
| grant_type | REQUIRED. Value: authorization_code |
| code | REQUIRED. The authorization code |
| code_verifier | REQUIRED if code_challenge was present |
| client_id | REQUIRED if not authenticating otherwise |
Validation
- Authenticate client if confidential
- Verify code issued to this client
- Verify code is valid and not expired
- Verify code_verifier (see PKCE skill)
- Return access token only once per code
Code Reuse Handling
def handle_code_reuse(code):
if code.already_used:
# MUST deny request
# SHOULD revoke all tokens from this code
revoke_tokens_for_code(code)
return error_response("invalid_grant")
Refresh Token Grant Specifics
Request Parameters
| Parameter | Requirement |
|-----------|-------------|
| grant_type | REQUIRED. Value: refresh_token |
| refresh_token | REQUIRED. The refresh token |
| scope | OPTIONAL. Must not exceed original scope |
Public Client Requirements
For public clients, refresh tokens MUST be either:
- Sender-constrained (bound to client proof)
- One-time use (rotation)
Refresh Token Rotation
def handle_refresh_token(refresh_token):
# Issue new access token
access_token = generate_access_token()
# Optionally rotate refresh token
if should_rotate:
new_refresh_token = generate_refresh_token()
invalidate(refresh_token)
return {
"access_token": access_token,
"refresh_token": new_refresh_token
}
return {"access_token": access_token}
Client Credentials Grant Specifics
Request Parameters
| Parameter | Requirement |
|-----------|-------------|
| grant_type | REQUIRED. Value: client_credentials |
| scope | OPTIONAL |
Requirements
- MUST only be used by confidential clients
- MUST authenticate client
Removed from OAuth 2.1
These grant types are NOT in OAuth 2.1:
- Implicit Grant (
response_type=token) - Resource Owner Password Credentials Grant
Implementation Checklist
- [ ] Use POST method only
- [ ] Accept application/x-www-form-urlencoded
- [ ] Use HTTPS (except localhost)
- [ ] Support CORS for browser apps
- [ ] Include Cache-Control: no-store
- [ ] Support authorization_code grant
- [ ] Support refresh_token grant
- [ ] Support client_credentials grant
- [ ] Validate PKCE code_verifier
- [ ] Enforce single-use authorization codes
- [ ] Revoke tokens on code reuse
- [ ] Handle refresh token rotation for public clients
- [ ] Return proper error responses
- [ ] Ignore unrecognized parameters
Scan to join WeChat group