OAuth2 and OpenID Connect (OIDC) Flow Testing: A Comprehensive Analysis of Response Types and Modes

When implementing OAuth2 authorization and OpenID Connect (OIDC) authentication, developers face numerous configuration choices that significantly impact both security and user experience. While the OAuth2 and OIDC specifications provide guidance, understanding how different combinations of response types and response modes behave in practice requires empirical testing.

This post presents a systematic analysis of OAuth2/OIDC flows using Google’s implementation, revealing key insights about token delivery patterns, security implications, and practical recommendations for production deployments.

The

Challenge: Too Many Options, Too Little Clarity

OAuth2 and OpenID Connect (OIDC) offer multiple response types and response modes that serve different purposes:

OAuth2 Response Types: - code - Authorization Code Grant (for access tokens) - token - Implicit Grant (direct access tokens)

OIDC Response Types: - id_token - Identity tokens for authentication - code token, code id_token, token id_token, code token id_token - Hybrid flows

Response Modes (both OAuth2/OIDC): - query, fragment, form_post - Different delivery methods

This creates a matrix of possibilities: 7 response types × 3 response modes × various scope combinations = 42+ different flow configurations to understand. Rather than rely on documentation alone, I built an automated testing framework to examine how these flows actually behave.

Methodology:

Automated OAuth2/OIDC Flow Testing

I created a Python script that systematically tests all OAuth2/OIDC flow combinations:

# Core testing parameters
RESPONSE_TYPES = ['code', 'token', 'id_token', 'code token', 'code id_token', 'token id_token', 'code token id_token']
RESPONSE_MODES = ['query', 'fragment', 'form_post']
SCOPES = ['profile email', 'openid profile email']

The testing framework:

  • Generates authorization URLs for each OAuth2/OIDC combination

  • Opens browser windows for user authentication

  • Captures responses across query parameters, form data, and URL fragments

  • Performs token exchange when authorization codes are present (OAuth2 code flow)

  • Validates ID tokens when present (OIDC authentication)

  • Logs comprehensive results for analysis

This approach provided empirical data on how Google’s OAuth2/OIDC implementation handles each flow configuration.

Key Findings: Response

Behavior Patterns

Pattern 1: Location

Predictability

The testing revealed consistent patterns in where tokens are delivered:

Query/Fragment Modes: - Single code responses → Query parameters - Any token-containing responses → URL fragments - Multiple tokens → Always delivered together

Form Post Mode: - All responses → Form parameters (consistent delivery)

Pattern 2: Token Grouping

When multiple tokens are requested (response_type=code token id_token), they are always delivered together in the same location. You never see mixed delivery like code in query parameters while tokens appear in fragments.

Pattern 3: OIDC Scope

Normalization

Regardless of requested scope, Google’s OIDC implementation consistently: - Adds openid scope to all responses (making them OIDC flows) - Expands scopes to include full URL versions - Maintains identical behavior for profile email vs openid profile email - Always includes identity information when ID tokens are requested

Detailed Test Results

The comprehensive testing revealed distinct patterns across all response mode and type combinations:

Query Response Mode Results

Response TypeScopeResponse LocationReturned TokensToken Exchange
codeprofile emailquery paramscodeaccess_token, id_token
codeopenid profile emailquery paramscodeaccess_token, id_token
tokenboth scopesfragmentaccess_tokennone
id_tokenboth scopesfragmentid_tokennone
code tokenboth scopesfragmentcode, access_tokenaccess_token, id_token
code id_tokenboth scopesfragmentcode, id_tokenaccess_token, id_token
token id_tokenboth scopesfragmentaccess_token, id_tokennone
code token id_tokenboth scopesfragmentcode, access_token, id_tokenaccess_token, id_token

Fragment Response Mode

Results

Response TypeScopeResponse LocationReturned TokensToken Exchange
codeboth scopesquery paramscodeaccess_token, id_token
tokenboth scopesfragmentaccess_tokennone
id_tokenboth scopesfragmentid_tokennone
code tokenboth scopesfragmentcode, access_tokenaccess_token, id_token
code id_tokenboth scopesfragmentcode, id_tokenaccess_token, id_token
token id_tokenboth scopesfragmentaccess_token, id_tokennone
code token id_tokenboth scopesfragmentcode, access_token, id_tokenaccess_token, id_token

Form Post Response Mode

Results

Response TypeScopeResponse LocationReturned TokensToken Exchange
codeboth scopesform paramscodeaccess_token, id_token
tokenboth scopesform paramsaccess_tokennone
id_tokenboth scopesform paramsid_tokennone
code tokenboth scopesform paramscode, access_tokenaccess_token, id_token
code id_tokenboth scopesform paramscode, id_tokenaccess_token, id_token
token id_tokenboth scopesform paramsaccess_token, id_tokennone
code token id_tokenboth scopesform paramscode, access_token, id_tokenaccess_token, id_token

Key Observations from

Results

  • OAuth2 Code Exchange Pattern: Any flow that includes “code” triggers a token exchange, always returning both access_token and id_token (full OIDC response)

  • OIDC Response Location Consistency: Code-only responses go to query_params in query/fragment modes, while any responses containing tokens or ID tokens are delivered via URL fragments (regardless of whether response_mode is query or fragment). Only form_post mode consistently delivers all response types via the specified method.

  • Scope Independence: No difference in behavior between “profile email” and “openid profile email” - OpenID scope is always included, making all flows OIDC-compliant

Security

Analysis: Most and Least Recommended OAuth2/OIDC Flows

🏆 Most

Recommended: Authorization Code Flow with Form Post

Configuration: response_type=code, response_mode=form_post

Why it’s secure: - No tokens in URLs or browser history - Credentials don’t appear in server logs - Backend token exchange enables client authentication (OAuth2) - Authorization code is short-lived and single-use - Full OIDC compliance with secure ID token delivery

Implementation benefits: - Simple to implement and debug - Reliable across all browsers - No client-side token handling required - Works for both OAuth2 authorization and OIDC authentication

⚠️ Least

Recommended: Implicit and OIDC Implicit Flows

Configuration: response_type=token or response_type=id_token

Security concerns: - Tokens/ID tokens exposed in URL fragments - No client authentication possible - Higher risk of token leakage - ID tokens may be logged in browser history (OIDC security risk)

Functional limitations: - No refresh tokens available - Shorter token lifetimes required - Limited error handling capabilities - Deprecated in OAuth 2.1 draft

Practical Recommendations

For Web Applications

// Recommended: Authorization Code with Form Post + PKCE
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `response_type=code&` +
  `response_mode=form_post&` +
  `client_id=${clientId}&` +
  `redirect_uri=${redirectUri}&` +
  `scope=openid profile email&` +
  `code_challenge=${codeChallenge}&` +
  `code_challenge_method=S256&` +
  `state=${state}`;

For Single Page Applications

(SPAs)

// Recommended: Use Backend-for-Frontend (BFF) Pattern
// SPA communicates with your BFF, BFF handles OAuth2/OIDC with provider
// BFF uses the secure web application flow:

const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `response_type=code&` +
  `response_mode=form_post&` +
  `client_id=${clientId}&` +
  `redirect_uri=${bffRedirectUri}&` +  // Points to BFF, not SPA
  `scope=openid profile email&` +
  `code_challenge=${codeChallenge}&` +
  `code_challenge_method=S256&` +
  `state=${state}`;

// Alternative: If BFF not feasible, Authorization Code + PKCE directly in SPA
const spaAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `response_type=code&` +
  `response_mode=fragment&` +
  `client_id=${clientId}&` +
  `redirect_uri=${redirectUri}&` +
  `scope=openid profile email&` +
  `code_challenge=${codeChallenge}&` +
  `code_challenge_method=S256&` +
  `state=${state}`;

Note: For SPAs, the Backend-for-Frontend (BFF) pattern is the recommended approach where your backend acts as the OAuth2/OIDC Relying Party. The BFF can safely store client secrets, handle token exchange, and manage session state. This avoids browser-based security limitations and provides the most secure implementation. If BFF is not feasible, use Authorization Code + PKCE directly in the SPA, though this may require workarounds for provider-specific limitations like Google’s client secret requirement.

Security Enhancements

Regardless of chosen flow, implement these security measures:

  • Always use PKCE for code-based flows (OAuth2/OIDC)

  • Implement state parameter for CSRF protection

  • Use nonce parameter for OIDC ID token replay protection

  • Validate redirect URIs strictly

  • Keep authorization codes short-lived (< 10 minutes)

  • Verify ID token signatures and claims (OIDC)

  • Validate token audiences and issuers

Response Mode Comparison

Response ModeSecurityReliabilityUse Case
form_post✅ Highest✅ ExcellentWeb applications
fragment⚠️ Medium✅ GoodSPAs, mobile apps
query❌ Lowest✅ GoodLegacy systems only

Testing Your Own

OAuth2/OIDC Implementation

The testing framework can be adapted for other OAuth2/OIDC providers:

# Configuration for different providers
PROVIDERS = {
    'google': {
        'auth_endpoint': 'https://accounts.google.com/o/oauth2/v2/auth',
        'token_endpoint': 'https://oauth2.googleapis.com/token'
    },
    'microsoft': {
        'auth_endpoint': 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
        'token_endpoint': 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
    }
}

Conclusion

This systematic analysis reveals that OAuth2/OIDC flow selection significantly impacts both security and implementation complexity. Key takeaways:

  • Form Post mode provides the best security profile for both OAuth2 and OIDC flows

  • Authorization code flows remain the gold standard for secure authentication and authorization

  • Implicit flows should be avoided in favor of code-based alternatives (deprecated in OAuth 2.1)

  • OIDC scope normalization means most flows become OpenID Connect flows automatically

  • Consistent testing is essential for understanding provider-specific behavior

The OAuth2/OIDC landscape continues evolving, with new security enhancements like PKCE and PAR (Pushed Authorization Requests) becoming standard. However, the fundamental principles revealed through this testing remain relevant: prioritize security, minimize token exposure, understand the difference between OAuth2 authorization and OIDC authentication, and thoroughly test your chosen configuration.

For production deployments, always combine empirical testing with security best practices to ensure robust authentication and authorization flows that protect both your application and your users.


The complete testing framework and detailed results are available in the Gist page. Feel free to adapt the testing methodology for your own OAuth2 implementations.