๐ OWASP API Security: OAuth 2.0 Authentication Flows
๐ Introduction to OAuth 2.0
OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user
accounts on an HTTP service. This comprehensive guide explores the OAuth 2.0 Playground and various
authentication flows used in modern API security.
Understanding these flows is critical for both securing your applications and identifying potential
vulnerabilities during security assessments.
๐ฏ OAuth 2.0 Playground Overview
The OAuth 2.0 Playground provides a step-by-step interactive environment to understand how different
OAuth flows work. It allows you to experiment with various grant types and see the actual requests and
responses.
Initial Setup: Client Registration
Before starting any OAuth flow, you must register a client application:
Step 1: Navigate to OAuth 2.0 Playground and register a new client
Client Information Received:
- Client ID: Unique identifier for your application
- Client Secret: Confidential key used for authentication
- Redirect URI: Where users are sent after authorization
โ ๏ธ Security Note: Keep your client information open in a separate window for easy
reference during the authentication process. The Client Secret should always be kept confidential and
never exposed in client-side code.
๐ OAuth 2.0 Flow Types
OAuth 2.0 supports several grant types, each designed for different use cases:
| Flow Type |
Use Case |
Security Level |
| Authorization Code |
Server-side web applications |
High |
| PKCE (Proof Key for Code Exchange) |
Mobile and native applications |
Very High |
| Implicit |
Legacy single-page applications (deprecated) |
Low |
| Device Code |
Input-constrained devices (smart TVs, IoT) |
Medium |
| OpenID Connect |
Authentication + Authorization |
High |
1๏ธโฃ Authorization Code Flow
The Authorization Code flow is the most secure and commonly used OAuth 2.0 flow for server-side
applications.
Authorization Code Flow Diagram
User initiates login
โ
Application redirects to authorization server
โ
User authenticates and grants permission
โ
Authorization code returned to application
โ
Application exchanges code for access token
โ
Access token granted
Step-by-Step Process:
Step 1: Initial Authorization Request
GET
/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}
Request Parameters:
- response_type=code: Indicates authorization code flow
- client_id: Your registered client identifier
- redirect_uri: Where to redirect after authorization
- scope: Permissions being requested (e.g., "photos")
- state: CSRF protection token
Step 2: User Authentication
POST /login - Username: NotTheWildBeast66 - Password: [user_password]
The user is presented with a login screen where they enter their credentials.
Step 3: Consent Grant
After successful authentication, the user is asked to grant permissions:
Consent Screen Example:
"This application would like the ability to access your photos."
User confirms: YES, GRANT ACCESS
Step 4: State Verification
Verify state parameter matches session value to protect against CSRF attacks
๐ Security Checkpoint: The state parameter acts as a CSRF token. The application must
verify that the state returned in the redirect matches the original state stored in the user session
(typically in a cookie).
Step 5: Code Exchange
POST /token -
grant_type=authorization_code&code={AUTH_CODE}&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&redirect_uri={REDIRECT_URI}
Token Response:
- access_token: Token to access protected resources
- token_type: Usually "Bearer"
- expires_in: Token validity period (seconds)
- scope: Granted permissions
2๏ธโฃ OpenID Connect Flow
OpenID Connect is an identity layer built on top of OAuth 2.0, providing authentication in addition to
authorization.
Key Differences from Standard OAuth:
- Includes an ID Token in addition to access token
- Uses nonce parameter for replay attack protection
- Provides user identity information
Step 1: Authorization Request
GET
/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=openid profile
email&state={STATE}&nonce={NONCE}
Additional Parameters:
- scope=openid: Required for OpenID Connect
- nonce: Random value to prevent replay attacks
Step 2: User Authentication
POST /login - Username: InquisitiveLizard66 - Password: [user_password]
Step 3: Grant Access
User confirms consent for requested scopes
Step 4: State Verification
Verify state parameter matches to prevent CSRF attacks
Step 5: Token Exchange
POST /token -
grant_type=authorization_code&code={AUTH_CODE}&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&redirect_uri={REDIRECT_URI}
OpenID Connect Response:
- access_token: OAuth 2.0 access token
- id_token: JWT containing user identity claims
- token_type: Bearer
- expires_in: Token expiration
3๏ธโฃ PKCE Flow (Proof Key for Code Exchange)
PKCE enhances the authorization code flow by adding an additional layer of security, particularly
important for mobile and native applications that cannot securely store client secrets.
PKCE Flow Diagram
Generate code_verifier (random string)
โ
Create code_challenge = hash(code_verifier)
โ
Send code_challenge in authorization request
โ
User authenticates and authorizes
โ
Send code_verifier with token request
โ
Server verifies hash(code_verifier) = code_challenge
Step 1: Generate Code Verifier and Challenge
Generate code_verifier: Random cryptographically secure string (43-128 characters)
Generate code_challenge: BASE64URL(SHA256(code_verifier))
๐พ Important: Store the code_verifier securely (e.g., in a cookie) - you'll need it for
the token exchange step.
Step 2: Authorization Request
GET
/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}&code_challenge={CODE_CHALLENGE}&code_challenge_method=S256
Step 3: User Authentication
POST /login - User enters credentials and grants permission
Step 4: Token Exchange with Code Verifier
POST /token -
grant_type=authorization_code&code={AUTH_CODE}&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&code_verifier={CODE_VERIFIER}
PKCE Response:
- access_token: Access token for API calls
- refresh_token: Token to obtain new access tokens
- token_type: Bearer
- expires_in: Access token lifetime
๐ Refresh Token Note: When you request a new access token using a refresh token, you
will also receive a new refresh token. Always update your stored refresh token.
4๏ธโฃ Implicit Flow (Legacy - Not Recommended)
The Implicit flow was designed for browser-based applications but is now considered less secure and has
been superseded by the Authorization Code flow with PKCE.
โ ๏ธ Security Warning: The Implicit flow is deprecated and should not be used for new
applications. Access tokens are exposed in the URL fragment, making them vulnerable to leakage.
Step 1: Authorization Request
GET
/authorize?response_type=token&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}
Step 2: User Authentication and Consent
POST /login - User authenticates and grants permissions
Step 3: Immediate Token Response
Redirect:
{REDIRECT_URI}#access_token={TOKEN}&token_type=Bearer&expires_in={SECONDS}&scope={SCOPE}
Response Parameters (in URL Fragment):
- access_token: Immediately returned in URL
- token_type: Bearer
- expires_in: Token expiration time
- scope: Granted permissions
โ Do not use this flow for new implementations. Use
Authorization Code + PKCE instead.
5๏ธโฃ Device Code Flow
The Device Code flow is designed for input-constrained devices such as smart TVs, gaming consoles, or IoT
devices that lack a browser or have limited input capabilities.
Device Code Flow Diagram
Device requests device code
โ
Device displays user code and verification URL
โ
User visits URL on separate device (phone/computer)
โ
User enters code and authenticates
โ
Device polls token endpoint
โ
Token granted after user authorization
Step 1: Request Device Code
POST /device/code - client_id={CLIENT_ID}&scope={SCOPE}
Device Code Response:
- device_code: Code for the device to use when polling
- user_code: Short code for user to enter (e.g., "ABCD-1234")
- verification_uri: URL where user enters the code
- expires_in: How long the codes are valid
- interval: Minimum seconds between polling requests
Step 2: Display User Instructions
Device displays: "Visit {VERIFICATION_URI} and enter code: {USER_CODE}"
Step 3: Device Polls for Token
POST /token -
grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={DEVICE_CODE}&client_id={CLIENT_ID}
โฑ๏ธ Polling Behavior: The device must wait at least the specified interval (in seconds)
between polling requests. Initial requests will return "authorization_pending" until the user completes
authorization.
Step 4: User Authorization (on separate device)
User navigates to verification URI on phone/computer
User enters the user code displayed on the device
User authenticates and grants permissions
Step 5: Token Response
Successful Token Response:
- access_token: Bearer token for API access
- refresh_token: Token for obtaining new access tokens
- token_type: Bearer
- expires_in: Access token lifetime
๐ Security Considerations
State Parameter (CSRF Protection)
The state parameter is crucial for preventing Cross-Site Request Forgery (CSRF) attacks:
- Generate a unique, unpredictable value for each authorization request
- Store it securely in the user's session (e.g., in a cookie)
- Verify the returned state matches the stored value before proceeding
- Reject any requests where the state doesn't match
Client Credentials Protection
| Credential Type |
Storage Location |
Exposure Risk |
| Client ID |
Can be public |
Low |
| Client Secret |
Server-side only |
Critical - never expose |
| Access Token |
Secure storage, transmitted over HTTPS |
High if leaked |
| Refresh Token |
Highly secure storage |
Critical |
Token Lifecycle Management
- Access Tokens: Short-lived (typically 1 hour or less)
- Refresh Tokens: Longer-lived but must be rotated
- Token Rotation: Always replace refresh tokens when issuing new access tokens
- Token Revocation: Implement mechanisms to revoke compromised tokens
Common Vulnerabilities
๐จ Security Risks to Test For:
- Missing or weak state validation (CSRF)
- Exposed client secrets in client-side code
- Insufficient redirect URI validation
- Token leakage through insecure channels
- Lack of PKCE in public clients
- Using implicit flow instead of authorization code + PKCE
- Refresh token reuse without rotation
๐งช Testing OAuth Implementations
Key Areas to Test:
1. Authorization Endpoint Testing
Test for open redirects: Modify redirect_uri parameter to external domain
Test state parameter validation: Omit or modify state value
Test scope manipulation: Request unauthorized scopes
2. Token Endpoint Testing
Test authorization code reuse: Attempt to use same code twice
Test code_verifier validation: Send incorrect verifier in PKCE flow
Test client authentication: Attempt requests with invalid credentials
3. Token Security Testing
Test token expiration: Use expired tokens to access resources
Test token scope: Attempt to access resources beyond granted scope
Test refresh token rotation: Verify old refresh tokens are invalidated
4. PKCE Implementation Testing
Test without PKCE: Attempt authorization code flow without code_challenge
Test code challenge methods: Verify S256 is required (not plain)
Test verifier matching: Send mismatched code_verifier
๐ Best Practices Summary
| Practice |
Recommendation |
Why |
| Flow Selection |
Use Authorization Code + PKCE |
Most secure for all application types |
| State Parameter |
Always include and validate |
CSRF protection |
| Token Storage |
Secure, HTTP-only cookies or secure storage APIs |
Prevent XSS attacks |
| HTTPS |
Always use TLS/SSL |
Protect tokens in transit |
| Redirect URIs |
Whitelist exact matches |
Prevent redirect attacks |
| Token Lifetime |
Short-lived access tokens |
Limit impact of token theft |
| Refresh Tokens |
Implement rotation |
Detect token replay |
๐ Learning Path
Now that you understand OAuth 2.0 flows, the next steps in your learning journey include:
Recommended Next Steps:
- Python Labs: Practice implementing OAuth flows in Python
- Security Testing: Learn to identify OAuth vulnerabilities
- Token Analysis: Understand JWT structure and validation
- API Security: Explore OWASP API Security Top 10
๐ Note: It's important to start these labs yourself to gain hands-on experience. Focus
on reading and understanding Python code, as this will be essential for security testing and automation.
๐ Quick Reference Commands
Authorization Code Flow:
GET
/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}
POST /token -
grant_type=authorization_code&code={CODE}&client_id={CLIENT_ID}&client_secret={SECRET}&redirect_uri={REDIRECT_URI}
PKCE Flow:
Generate: code_verifier (random 43-128 chars) and code_challenge (SHA256 hash)
GET
/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}&code_challenge={CHALLENGE}&code_challenge_method=S256
POST /token -
grant_type=authorization_code&code={CODE}&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&code_verifier={VERIFIER}
Device Code Flow:
POST /device/code - client_id={CLIENT_ID}&scope={SCOPE}
POST /token -
grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={DEVICE_CODE}&client_id={CLIENT_ID}
Using Access Tokens:
Authorization: Bearer {ACCESS_TOKEN}
Refreshing Tokens:
POST /token -
grant_type=refresh_token&refresh_token={REFRESH_TOKEN}&client_id={CLIENT_ID}&client_secret={SECRET}