๐Ÿ” 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:

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:

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

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:

๐Ÿ“ 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}