OAuth 2.0 Grant Types
The grant_types attribute specifies which OAuth 2.0 flows (authorization grants) the client is permitted to use.
Standard Grant Types (RFC 6749)
authorization_code
The most common and secure flow
- User redirected to authorization server for authentication and consent
- Authorization server returns authorization code to client’s redirect URI
- Client exchanges code for tokens at token endpoint
- Supports refresh tokens
- Tokens never exposed to browser
- Works with PKCE for public clients
sequenceDiagram
participant User
participant Client
participant AuthServer as Authorization Server
participant API as Resource Server
User->>Client: 1. Click "Login"
Client->>AuthServer: 2. Redirect to /authorize
Note over AuthServer: response_type=code
User->>AuthServer: 3. Authenticate & consent
AuthServer->>Client: 4. Redirect with code
Note over Client: https://app.com/callback?code=ABC123
Client->>AuthServer: 5. POST /token (exchange code)
Note over Client,AuthServer: Code + client credentials
AuthServer->>Client: 6. Return tokens
Note over Client: access_token, refresh_token, id_token
Client->>API: 7. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 8. Protected resource
Flow:
- Client redirects user to authorization endpoint
- User authenticates and grants consent
- Authorization server redirects back with code
- Client exchanges code for access token (and optionally refresh token)
Best for:
- Web applications (confidential clients)
- Single Page Applications with PKCE
- Mobile applications with PKCE
- Native applications with PKCE
Example Registration:
{
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}
Authorization Code with PKCE
Proof Key for Code Exchange - Required for public clients
PKCE (RFC 7636) adds cryptographic protection to the authorization code flow, preventing authorization code interception attacks. It’s required for public clients (SPAs, mobile apps) and recommended for all clients.
sequenceDiagram
participant User
participant Client
participant AuthServer as Authorization Server
participant API as Resource Server
Note over Client: Generate PKCE parameters
Note over Client: code_verifier = random(43-128 chars)
Note over Client: code_challenge = BASE64URL(SHA256(code_verifier))
User->>Client: 1. Click "Login"
Client->>AuthServer: 2. Redirect to /authorize + PKCE
Note over AuthServer: response_type=code
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
code_challenge_method=S256
AuthServer->>AuthServer: 3. Store code_challenge with code
User->>AuthServer: 4. Authenticate & consent
AuthServer->>Client: 5. Redirect with code
Note over Client: https://app.com/callback?code=ABC123
Client->>AuthServer: 6. POST /token (exchange code + verifier)
Note over Client,AuthServer: Code + code_verifier (original random string)
rect rgb(200, 255, 200)
Note over AuthServer: 7. Verify PKCE
Note over AuthServer: Compute SHA256(code_verifier)
Compare with stored code_challenge
✓ Must match!
end
AuthServer->>Client: 8. Return tokens
Note over Client: access_token, refresh_token, id_token
Client->>API: 9. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 10. Protected resource
PKCE Flow:
- Client generates random
code_verifier(43-128 character string) - Client computes
code_challenge = BASE64URL(SHA256(code_verifier)) - Client sends
code_challengein authorization request - Authorization server stores
code_challengewith authorization code - Client exchanges code +
code_verifierfor tokens - Authorization server verifies
SHA256(code_verifier)matches storedcode_challenge
Why PKCE?
- Prevents authorization code interception: Even if attacker steals the code, they can’t exchange it without the
code_verifier - No client secret needed: Safe for public clients (SPAs, mobile apps) that can’t store secrets
- Platform-agnostic: Works on any platform without secure storage
- Required for public clients in OAuth 2.1 specification
Example Authorization Request:
GET /authorize?
response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=https://app.com/callback
&scope=openid profile email
&state=af0ifjsldkj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
Example Token Request:
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=ABC123
&redirect_uri=https://app.com/callback
&client_id=s6BhdRkqt3
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
PKCE Methods:
S256(SHA-256):code_challenge = BASE64URL(SHA256(code_verifier))- Recommendedplain:code_challenge = code_verifier- Only use if SHA-256 unavailable
implicit
⚠️ DEPRECATED - Do not use
- Tokens returned directly in redirect URI fragment (no code exchange)
- Access token exposed in browser history and logs
- No refresh tokens
- No client authentication
- Originally designed for SPAs before PKCE existed
sequenceDiagram
participant User
participant Client
participant AuthServer as Authorization Server
participant API as Resource Server
User->>Client: 1. Click "Login"
Client->>AuthServer: 2. Redirect to /authorize
Note over AuthServer: response_type=token
User->>AuthServer: 3. Authenticate & consent
AuthServer->>Client: 4. Redirect with tokens in URL fragment
Note over Client: https://app.com/callback#access_token=XYZ789
Note over Client: ⚠️ Token exposed in browser!
rect rgb(255, 200, 200)
Note over Client: No token endpoint exchange
Note over Client: No client authentication
Note over Client: No refresh token
end
Client->>API: 5. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 6. Protected resource
Status: Removed in OAuth 2.1 specification
Migration: Use authorization_code with PKCE instead
password (Resource Owner Password Credentials)
⚠️ Use with extreme caution
- User provides username and password directly to client application
- Client sends credentials to token endpoint
- Token endpoint returns access token (and optionally refresh token)
- Bypasses authorization server’s login page
- No interactive consent
sequenceDiagram
participant User
participant Client
participant AuthServer as Authorization Server
participant API as Resource Server
User->>Client: 1. Enter username & password
Note over User,Client: User enters credentials in client app
Client->>AuthServer: 2. POST /token
Note over Client,AuthServer: grant_type=password
username=johndoe
password=***
client_id=xyz
rect rgb(255, 240, 200)
Note over AuthServer: ⚠️ Client handles user credentials directly
Note over AuthServer: Only for trusted first-party apps!
end
AuthServer->>Client: 3. Return tokens
Note over Client: access_token, refresh_token
Client->>API: 4. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 5. Protected resource
When to use:
- ✅ Highly trusted first-party applications only
- ✅ Migration path from legacy authentication
- ❌ Never for third-party applications
- ❌ Avoid if
authorization_codeflow is possible
Example Request:
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=password
&username=johndoe
&password=A3ddj3w
&client_id=s6BhdRkqt3
Example Registration:
{
"grant_types": ["password", "refresh_token"],
"response_types": []
}
client_credentials
Machine-to-machine authentication
- No user context - client acts on its own behalf
- Client exchanges its own credentials for access token
- Used for service-to-service communication
- No refresh tokens (just request new access token when needed)
- No authorization code or redirect
sequenceDiagram
participant Client as Client Service
participant AuthServer as Authorization Server
participant API as Resource Server
Note over Client: No user involved
Client->>AuthServer: 1. POST /token
Note over Client,AuthServer: grant_type=client_credentials
client_id=service123
client_secret=***
scope=api:read api:write
AuthServer->>AuthServer: 2. Verify client credentials
AuthServer->>Client: 3. Return access_token
Note over Client: access_token only
(no refresh_token)
Client->>API: 4. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 5. Protected resource
Note over Client: When token expires,
request new token
Flow:
- Client authenticates directly at token endpoint
- Token endpoint returns access token
- Client uses access token to call APIs
Best for:
- Backend services
- API-to-API communication
- Batch jobs
- Daemon processes
- Service accounts
Example Request:
POST /token HTTP/1.1
Host: authorization-server.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&scope=api:read api:write
Example Registration:
{
"grant_types": ["client_credentials"],
"response_types": [],
"token_endpoint_auth_method": "private_key_jwt"
}
refresh_token
Exchange refresh token for new access token
- Not a standalone flow - used with other grant types
- Allows obtaining new access tokens without re-authentication
- Must be explicitly requested in scope or grant_types
- Enables long-lived sessions
- Can be revoked by authorization server
sequenceDiagram
participant Client
participant AuthServer as Authorization Server
participant API as Resource Server
Note over Client: Client previously obtained
refresh_token from authorization_code
or password grant
Client->>API: 1. Call API with access_token
API->>Client: 2. 401 Unauthorized (token expired)
Client->>AuthServer: 3. POST /token (refresh)
Note over Client,AuthServer: grant_type=refresh_token
refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
client_id=xyz
AuthServer->>AuthServer: 4. Validate refresh_token
AuthServer->>Client: 5. Return new tokens
Note over Client: New access_token
Optionally: new refresh_token
Client->>API: 6. Retry API call with new access_token
Note over Client,API: Authorization: Bearer {new_token}
API->>Client: 7. Protected resource
Flow:
- Client receives refresh token from initial authorization
- When access token expires, client uses refresh token at token endpoint
- Token endpoint returns new access token (and optionally new refresh token)
Example Request:
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=s6BhdRkqt3
Supported with:
- ✅
authorization_code - ✅
password - ❌
implicit(implicit never provides refresh tokens) - ❌
client_credentials(just request new access token)
Extended Grant Types
urn:ietf:params:oauth:grant-type:jwt-bearer (RFC 7523)
JWT Bearer Token Flow
- Exchange a JWT for an access token
- JWT can be self-issued or from another authorization server
- Used for federation and delegation scenarios
- Common in enterprise identity systems
sequenceDiagram
participant Client as Client/Service
participant IDP as Identity Provider
participant AuthServer as Authorization Server
participant API as Resource Server
Note over Client: Scenario: Service wants to act
on behalf of user from another IDP
Client->>IDP: 1. Obtain JWT assertion
Note over IDP: User authenticated here
IDP->>Client: 2. Return signed JWT
Note over Client: JWT contains subject (user),
issuer, audience, expiry
Client->>AuthServer: 3. POST /token
Note over Client,AuthServer: grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0...
AuthServer->>AuthServer: 4. Validate JWT signature & claims
Note over AuthServer: Verify issuer, audience,
expiry, signature
AuthServer->>Client: 5. Return access_token
Client->>API: 6. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 7. Protected resource
Use cases:
- Service account impersonation
- Cross-domain authentication
- Token exchange between systems
Example Request:
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0...
urn:ietf:params:oauth:grant-type:saml2-bearer (RFC 7522)
SAML 2.0 Bearer Assertion Flow
- Exchange a SAML 2.0 assertion for an access token
- Used for SAML-to-OAuth bridging
- Common in enterprise identity federation
- Allows SAML-based systems to access OAuth-protected APIs
sequenceDiagram
participant User
participant Client
participant SAML_IDP as SAML Identity Provider
participant AuthServer as Authorization Server
participant API as Resource Server
User->>Client: 1. Access application
Client->>SAML_IDP: 2. SAML authentication request
User->>SAML_IDP: 3. Authenticate
SAML_IDP->>Client: 4. SAML assertion (XML)
Note over Client: SAML assertion contains
user identity & attributes
Client->>AuthServer: 5. POST /token
Note over Client,AuthServer: grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer
assertion=[base64-encoded-SAML]
AuthServer->>AuthServer: 6. Validate SAML assertion
Note over AuthServer: Verify signature, issuer,
audience, conditions
AuthServer->>Client: 7. Return access_token
Client->>API: 8. Call API with access_token
Note over Client,API: Authorization: Bearer {token}
API->>Client: 9. Protected resource
Use cases:
- Enterprise SSO integration
- Legacy SAML systems accessing modern APIs
- Federation across identity providers
urn:ietf:params:oauth:grant-type:device_code (RFC 8628)
Device Authorization Flow
- For input-constrained devices (smart TVs, IoT devices, CLI tools)
- Device displays a user code
- User enters code on another device (phone, computer)
- Device polls token endpoint until user completes authorization
sequenceDiagram
participant Device as Smart TV/IoT Device
participant AuthServer as Authorization Server
participant User
participant Browser as User's Browser/Phone
participant API as Resource Server
Device->>AuthServer: 1. POST /device_authorization
Note over Device,AuthServer: client_id=device123
AuthServer->>Device: 2. Return device_code & user_code
Note over Device: device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
user_code=WDJB-MJHT
verification_uri=https://example.com/device
Device->>User: 3. Display user_code & URL
Note over Device,User: "Visit example.com/device
Enter code: WDJB-MJHT"
User->>Browser: 4. Open verification_uri
Browser->>AuthServer: 5. GET /device
User->>Browser: 6. Enter user_code
Browser->>AuthServer: 7. Submit code
User->>Browser: 8. Authenticate & consent
loop Poll every 5 seconds
Device->>AuthServer: 9. POST /token (poll)
Note over Device,AuthServer: grant_type=urn:ietf:params:oauth:grant-type:device_code
device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
AuthServer->>Device: 10a. authorization_pending (keep polling)
end
AuthServer->>Device: 10b. Return tokens
Note over Device: access_token, refresh_token
Device->>API: 11. Call API with access_token
API->>Device: 12. Protected resource
Flow:
- Device requests device code from authorization server
- Device displays user code and verification URL
- User visits URL on another device and enters code
- Device polls token endpoint
- Once user approves, device receives access token
Best for:
- Smart TVs
- IoT devices
- Command-line tools
- Devices without browsers
Example Registration:
{
"grant_types": [
"urn:ietf:params:oauth:grant-type:device_code",
"refresh_token"
],
"response_types": []
}
urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693)
Token Exchange Flow
- Exchange one token for another
- Supports impersonation and delegation
- Used for microservices calling other services on behalf of user
- Supports actor tokens for complex delegation chains
sequenceDiagram
participant User
participant ServiceA as Service A (Frontend)
participant AuthServer as Authorization Server
participant ServiceB as Service B (Backend)
participant ServiceC as Service C (Data Layer)
User->>ServiceA: 1. Request with access_token
Note over ServiceA: Token has broad permissions
ServiceA->>AuthServer: 2. POST /token (exchange)
Note over ServiceA,AuthServer: grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=original_access_token
subject_token_type=urn:ietf:params:oauth:token-type:access_token
scope=serviceB:read (downscoped)
AuthServer->>AuthServer: 3. Validate & create new token
Note over AuthServer: New token has reduced scope
for ServiceB only
AuthServer->>ServiceA: 4. Return new access_token
Note over ServiceA: Token scoped for ServiceB
ServiceA->>ServiceB: 5. Call ServiceB with new token
Note over ServiceA,ServiceB: Authorization: Bearer {downscoped_token}
ServiceB->>AuthServer: 6. POST /token (exchange again)
Note over ServiceB,AuthServer: actor_token=serviceB_token
subject_token=downscoped_token
scope=serviceC:write
AuthServer->>ServiceB: 7. Return token with actor chain
ServiceB->>ServiceC: 8. Call ServiceC
Note over ServiceB,ServiceC: Token shows: User -> ServiceA -> ServiceB
ServiceC->>ServiceB: 9. Response
ServiceB->>ServiceA: 10. Response
ServiceA->>User: 11. Final response
Use cases:
- Microservice architecture (service A calls service B on behalf of user)
- Token downscoping (reduce scope/permissions)
- Token type conversion (JWT to opaque, etc.)
Example Request:
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=accVkjcJyb4BWCxGsndESCJQbdFMogUC5PbRDqceLTC
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&scope=api:read
Grant Type Comparison
| Grant Type | User Context | Redirect | Use Case | Security |
|---|---|---|---|---|
authorization_code | ✅ Yes | ✅ Yes | Web/mobile apps | 🔒🔒🔒🔒🔒 |
implicit | ✅ Yes | ✅ Yes | DEPRECATED | ❌ |
password | ✅ Yes | ❌ No | Trusted first-party only | 🔒🔒 |
client_credentials | ❌ No | ❌ No | Service accounts | 🔒🔒🔒🔒 |
refresh_token | ✅ Yes | ❌ No | Token refresh | 🔒🔒🔒🔒 |
device_code | ✅ Yes | ❌ No | Input-limited devices | 🔒🔒🔒🔒 |
Typical Combinations
Web Application (Confidential Client)
{
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "private_key_jwt"
}
Single Page Application (Public Client)
{
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
Note: Must use PKCE
Mobile/Native Application (Public Client)
{
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
Note: Must use PKCE and platform-specific best practices
Backend Service/API (Confidential Client)
{
"grant_types": ["client_credentials"],
"response_types": [],
"token_endpoint_auth_method": "private_key_jwt"
}
IoT/Smart TV Device
{
"grant_types": [
"urn:ietf:params:oauth:grant-type:device_code",
"refresh_token"
],
"response_types": [],
"token_endpoint_auth_method": "none"
}
Legacy First-Party App (Migration Scenario)
{
"grant_types": ["password", "refresh_token"],
"response_types": [],
"token_endpoint_auth_method": "client_secret_basic"
}
Note: Migrate to authorization_code as soon as possible
Security Recommendations
✅ Recommended
- Use
authorization_codefor all user-facing applications - Use PKCE with
authorization_codefor public clients - Use
client_credentialsfor service accounts - Use
refresh_tokenwhen long-lived sessions are needed - Use
device_codefor input-constrained devices
⚠️ Use with Caution
password- Only for highly trusted first-party applications- Migration scenarios only
- Consider
authorization_codeinstead
❌ Do Not Use
implicit- Deprecated in OAuth 2.1- Replaced by
authorization_codewith PKCE - No security benefits, multiple vulnerabilities