Skip to main content
AllDevToolsHub
๐Ÿ”

OAuth 2.0 PKCE Debugger

Browser-to-Target

Visually debug OAuth 2.0 and PKCE authorization flows.

OAuth 2.0 PKCE Debugger
OAuth 2.0 PKCE Debugger
Generate authorization URLs and debug PKCE flow parameters.
S256 Method Enabled
PKCE Exchange

Use the Challenge to start the flow, and the Verifier when exchanging the code for an access token.

Generated Auth URL

https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Falldevtoolshub.com%2Foauth-callback&scope=openid+profile+email&response_type=code&state=&code_challenge=&code_challenge_method=S256
Flow Explanation
1. Authorize

Browser redirects with code_challenge.

2. Code

App receives code via redirect URI.

3. Token

App sends code + verifier to get token.

Ready for test
RFC 7636
Try:
This tool helps debug OAuth and token flows by sending browser requests to the authorization or API endpoints you provide. Review the destination carefully before using real credentials.

Enter test data. Results update in real-time.

Overview

What is OAuth 2.0 PKCE Debugger?

Generate secure code verifiers and challenges for PKCE. Build authorization URLs and trace the handshake steps to debug your authentication implementation.
FAQ

Frequently Asked Questions

Reference

Technical Deep Dive

SECURITY & CRYPTO

OAuth 2.0 PKCE Debugger

Generate secure code verifiers and challenges for PKCE. Build authorization URLs and trace the handshake steps to debug your authentication implementation.

๐Ÿ”

Cryptographically Sound

Uses well-established algorithms and primitives, no homegrown crypto, no guesswork.

๐Ÿ›ก

Stays in Your Browser

Secrets, hashes, and keys never touch a server. Audit the Network tab, it's empty.

โš™

Tunable & Inspectable

Adjust parameters, inspect outputs, and verify against known vectors during integration.

OAuth 2.0 + PKCE: The Modern Authorization Standard

OAuth 2.0 is twelve years old; PKCE is ten. Together they're the standard way every modern application, web app, mobile app, single-page app, CLI, delegates authentication to an identity provider. The flow has a lot of moving parts, and most OAuth bugs are subtle: a wrong redirect URI, a missing state parameter, a mismatched code_verifier. This debugger lets you walk the handshake step-by-step.

The Authorization Code Flow with PKCE

  1. Client generates code_verifier. A cryptographically random string of 43-128 characters from the unreserved set. 32 random bytes base64url-encoded works perfectly.

  2. Client derives code_challenge. SHA-256(code_verifier) then base64url-encode (no padding). This is the value that goes to the auth server.

  3. Client redirects user to authorization endpoint with:

    • response_type=code (this is the auth code flow)
    • client_id=<your client ID>
    • redirect_uri=<registered URI> (must exactly match a pre-registered URI in your provider config)
    • scope=<requested permissions>
    • state=<random> (CSRF protection, required, not optional)
    • code_challenge=<derived above>
    • code_challenge_method=S256
  4. User authenticates with the provider (logs in, approves the consent screen). The provider redirects back to the redirect_uri with ?code=<auth_code>&state=<echoed>.

  5. Client verifies state. Must match what was sent. If not, abort, it's a CSRF attempt.

  6. Client exchanges code for tokens. POST to the token endpoint:

    • grant_type=authorization_code
    • code=<from step 4>
    • redirect_uri=<same as step 3>
    • client_id=<same>
    • code_verifier=<original from step 1>
    • (client_secret for confidential clients only, public clients omit it)
  7. Auth server validates. Hashes the provided code_verifier, compares to the code_challenge it stored at step 3. If they match, return access_token, refresh_token, id_token (for OIDC).

Each step has failure modes. The debugger helps you isolate which step is broken.

Common Failure Modes

"invalid_redirect_uri" at step 3. The redirect_uri parameter must exactly match (character-for-character) one of the URIs registered with the provider. Trailing slashes count. http://localhost:3000/callback โ‰  http://localhost:3000/callback/. Many providers also forbid wildcards; the URI must be exact.

"invalid_client" at step 6. Could be wrong client_id, wrong client_secret (for confidential clients), or, sneakier, the client is registered as "confidential" but you're trying public-client flow without secret. Provider config matters.

"invalid_grant" at step 6. Often means code_verifier mismatch. The verifier you're sending doesn't hash to the challenge the auth server stored. Common causes: re-generating the verifier between steps (use the SAME value from step 1), base64 padding bugs (the URL-safe variant must omit padding), encoding bugs (UTF-8 vs ASCII).

"invalid_request" at step 6. Missing required parameter. With PKCE: missing code_verifier. Without PKCE: missing client_secret. Read the actual error_description.

State mismatch at step 5. Either you didn't store state across the redirect (lost in session) or an attacker injected one. Either way, abort and start over.

Code already exchanged. Auth codes are single-use. If you accidentally exchange the same code twice, the second call fails. Common in React StrictMode (effects run twice in dev), guard the exchange call.

PKCE Across Provider Implementations

Most major providers support PKCE:

  • Google. Supports S256, recommends PKCE for SPAs and mobile.
  • GitHub. Supports PKCE since 2020. Required for public clients.
  • Microsoft Entra ID (Azure AD). PKCE mandatory for SPAs.
  • Auth0. Recommends PKCE; older flows still accepted but deprecated.
  • Okta. Same.
  • Apple Sign In. PKCE-aware.
  • Twitch, Discord, Slack. Variable; check their OAuth docs.

Some providers add custom parameters (Microsoft's tenant, Google's access_type for refresh tokens). The debugger doesn't enforce these, read the provider's OAuth docs.

State, Nonce, and Replay Protection

state prevents CSRF on the redirect: an attacker can't forge a callback to your app because they don't know the state value you generated and stored. Always include it.

nonce (OIDC only) prevents replay of the id_token. The provider embeds your nonce in the id_token's claims; you verify it matches. Include in the auth request, validate in the response.

Both should be cryptographically random. Both should be tied to the user's session (so an attacker can't replay across sessions).

Where Tokens Should Live

After step 7, you have tokens. Where you put them is its own security topic.

  • Access token. Short-lived (often 1 hour). Send with each API call as Authorization: Bearer .... For SPAs: in-memory (not localStorage, XSS risk). For mobile: secure storage (Keychain on iOS, Keystore on Android). For server-side: in the session.
  • Refresh token. Long-lived (hours to weeks). Used to get new access tokens without re-auth. Must be stored securely; localStorage is risky for SPAs. Pattern: keep refresh on server, return new access tokens via a same-origin endpoint.
  • id_token. OIDC user-identity claims. Validate signature, expiration, audience, issuer. Use for identity; don't send to APIs (that's what access_token is for).

The Backend-For-Frontend (BFF) pattern is increasingly popular for SPAs: a thin server-side layer holds the tokens; the SPA talks to BFF endpoints with same-origin session cookies; BFF talks to APIs with the OAuth tokens. Cleaner security boundary than browser-side token management.

Why "Implicit Flow" Is Dead

Original OAuth 2.0 had an "implicit flow" where the access token was returned directly in the URL fragment of the redirect, no code-for-token exchange step. Designed for SPAs that couldn't do server-side exchange. Dead now because:

  • Tokens in URL fragments leaked into browser history, referer headers, server logs.
  • No refresh tokens (you couldn't safely return them; same leak risk).
  • PKCE made the auth code flow safe for public clients, removing implicit's reason to exist.

OAuth 2.1 (the consolidation spec) formally deprecates implicit. If you're reading documentation that recommends it, that documentation is stale.

Privacy

This debugger does its work in your browser via the Web Crypto API: random verifier generation, SHA-256 hashing, base64url encoding. The constructed URLs are shown but not auto-navigated. Open DevTools Network: zero requests to this tool's origin. Any provider call (when you click an authorization URL) goes directly to the provider you specified.

03 Real-World Use Cases

OAuth failure modes are easy to diagnose only after you've seen each one. These are the four that account for ~90% of integration tickets.

  • ๐Ÿšง
    redirect_uri mismatch Provider returns error=redirect_uri_mismatch. The URI in your authorize call must match (character-for-character) one of the URIs registered in the provider's app settings. Trailing slashes, http vs https, port number, all count.
  • ๐Ÿ›ก
    state parameter as CSRF defense RFC 6749 ยง10.12 makes state non-optional in practice. Without it, an attacker can trigger a callback to your app with their own auth code and silently link their account to your victim's session.
  • ๐Ÿ“ฑ
    PKCE on mobile and SPA RFC 7636 PKCE is mandatory in the OAuth 2.1 draft for all public clients. Generate code_verifier (43-128 chars), derive code_challenge = BASE64URL(SHA256(verifier)), send with code_challenge_method=S256.
  • ๐Ÿ”„
    Refresh-token rotation Each refresh call returns a new refresh token; the old one is invalidated. If a stolen refresh is used, the legitimate client's next refresh fails, your server detects the breach and revokes the family. Standard practice since OAuth 2.1.
  • ๐Ÿ†”
    nonce for OIDC id_token replay defense OpenID Connect requires a nonce claim echoed back inside the id_token. Verifying it stops an attacker from replaying a captured id_token in a new session.

04 Worked Examples

EXAMPLE 1 ยท AUTHORIZATION CODE + PKCE END-TO-END
Step 1, client generates verifier & challenge (RFC 7636):
code_verifier  = base64url(random_bytes(32))
             // e.g. "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

code_challenge = base64url(sha256(code_verifier))
// e.g. "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
code_challenge_method = S256


Step 2, token exchange must echo the original verifier:

POST /token
grant_type=authorization_code
code=<received from /authorize callback>
redirect_uri=<same URI used at /authorize>
client_id=<public client id>
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The server recomputes SHA-256 of the supplied verifier and compares to the challenge it stored at step 1. Match โ†’ tokens returned. Mismatch โ†’ invalid_grant. The verifier is what proves the same client that started the flow is finishing it.




EXAMPLE 2 ยท STATE MISMATCH, WHAT GETS THROUGH WITHOUT IT

Attack without state (CSRF on the callback):

1. Attacker starts OAuth with their own account, gets ?code=ATTACKER
2. Tricks victim into clicking https://yourapp.com/callback?code=ATTACKER
3. Your app exchanges the code, links ATTACKER's identity into victim's session
4. Anything the victim uploads goes to attacker's account

Defense, generate per-session state, validate on callback:

// before /authorize
session.oauth_state = base64url(random_bytes(16));
// on callback
if (req.query.state !== session.oauth_state) {
return abort("state_mismatch"); // RFC 6749 ยง10.12
}

State is to the redirect what CSRF tokens are to forms. It must be unguessable, session-bound, and one-time, bind to SameSite=Lax cookie session so the attacker's forged callback can't carry it.




EXAMPLE 3 ยท DECODING error & error_description ON FAILURE

A failed callback URL from the provider:

https://app.example.com/callback
?error=invalid_scope
&error_description=The%20requested%20scope%20'admin:write'%20is%20not%20granted
&state=abc123

URL-decoded, human form (RFC 6749 ยง4.1.2.1 error codes):

error: invalid_scope
description: The requested scope 'admin:write' is not granted
state: abc123 (validate this matches before trusting any of the above)

Always URL-decode error_description before logging, providers percent-encode quotes and spaces. Standard error codes per RFC 6749: invalid_request, unauthorized_client, access_denied, unsupported_response_type, invalid_scope, server_error, temporarily_unavailable.




05 Related Tools

OAuth flows produce JWTs, get triggered by URLs, and end in API calls. These cover the surrounding terrain.

You Might Also Need