OAuth/OIDC Debugger

Debug OAuth 2.0 and OpenID Connect flows. Decode JWTs, generate PKCE challenges, validate redirect URIs, and troubleshoot OAuth errors.

Advertisement

Debug an OAuth 2.0 or OIDC flow: tokens, PKCE, redirect URIs and error codes

OAuth failures are frustrating because the error you get back is deliberately vague. invalid_grant covers at least four unrelated mistakes. A redirect URI mismatch is a single invisible character. A token that "does not work" is often a perfectly valid token with the wrong aud. This tool gives you five workbenches for those specific problems, all running in your browser.

What it does not do: it does not run a live OAuth flow. It does not hold client secrets, exchange authorization codes, call token endpoints, or host a callback. It builds requests and inspects artefacts — you paste tokens in and copy URLs out, and the network calls stay with your own application and your identity provider. Nothing you paste is transmitted anywhere.

The five tabs

TabInputWhat you get
JWT DecoderAn access token or ID tokenDecoded header and payload, expiry in human terms, structural warnings
PKCE GeneratorNothing — press generateA code verifier and its S256 challenge, ready to paste into a request
Flow TesterEndpoint, client ID, redirect URI, scopesA complete, correctly encoded authorization URL you can open
Redirect ValidatorRegistered URI and actual URIA component-by-component diff showing exactly what differs
Error DebuggerAn error code from your providerRanked probable causes and the fixes for each

Authorization code with PKCE is the answer to "which flow"

If you are choosing a flow today, the choice is essentially made for you. Authorization code flow with PKCE is the current default for every client type — single-page apps, mobile apps, and confidential server-side apps alike. The Flow Tester offers it first for that reason.

The plain authorization code flow is still correct for a confidential client that can keep a secret on a server, and it is offered here, but adding PKCE costs nothing and closes a real attack. Implicit flow is included and marked deprecated: it returns tokens directly in the URL fragment, which puts them in browser history, referrer headers and logs, with no way to authenticate the client at redemption. If you are still on it, migrating is the work.

PKCE (RFC 7636) exists because a public client cannot hold a secret. Anyone who intercepts the authorization code — through a hijacked custom URL scheme on mobile, a malicious app registered for the same scheme, or a leaky redirect — could otherwise redeem it. PKCE binds the code to the specific client instance that started the flow:

  • Before redirecting, the client generates a random code verifier.
  • It sends code_challenge = base64url(SHA-256(verifier)) and code_challenge_method=S256 on the authorization request.
  • The authorization server stores the challenge alongside the issued code.
  • At the token endpoint, the client presents the original verifier. The server hashes it and compares.

An intercepted code is useless without the verifier, which never left the client. The generator here produces the verifier from 32 bytes of crypto.getRandomValues — the browser's cryptographic random source, not Math.random — and base64url-encodes it, giving a 43-character verifier. The challenge is computed with the Web Crypto SHA-256 implementation. There is a plain option for providers that do not support S256; it is marked as weaker because plain sends the verifier itself as the challenge, which an interceptor of the authorization request can simply read.

The verifier must be 43 to 128 characters using only A-Z a-z 0-9 - . _ ~. If you generate your own and hit "the code_verifier is invalid", check the length and the character set first — standard base64 output containing +, / or = is the usual culprit, which is why base64url encoding is required.

Access token, ID token, refresh token

Conflating these causes more OIDC bugs than anything else, so be precise:

Access tokenID tokenRefresh token
AnswersWhat may this client do?Who signed in, and how?May I have new tokens?
AudienceThe API / resource serverYour client applicationThe authorization server
Who validates itThe API you callYour client, once at sign-inThe token endpoint
FormatOpaque or JWT — provider's choiceAlways a JWT (OIDC requires it)Opaque, essentially always
LifetimeShort — minutes to an hourShort; it is a login receipt, not a sessionLong, and often single-use with rotation

Three rules that follow from that table and prevent most of the damage:

  • Never send an ID token to an API as authorization. Its audience is your client. An API that accepts it is accepting a token minted for someone else's audience, which is precisely the check that stops token substitution attacks.
  • Never treat an ID token as a session. It says a login happened at a point in time. Exchange it for your own session and stop re-reading it.
  • Do not parse access tokens in your client. They are opaque by contract even when they happen to decode as JWTs today. A provider can change the format without notice and your client will break. Read user data from the ID token or the userinfo endpoint.

Reading the claims

Paste a token into the decoder and it splits on the dots, base64url-decodes the header and payload, and shows both as formatted JSON along with the raw signature segment. Timestamp claims are rendered as ISO dates plus a relative phrase — "expired 14 minutes ago", "expires in 3 hours" — because reading Unix seconds by eye is where mistakes happen.

The claims worth checking, in the order they usually break things:

  • aud — the intended recipient. If your API rejects a token that looks fine, compare this against the identifier the API expects, character for character.
  • iss — the issuer. Must exactly match the expected issuer string, including trailing-slash presence and tenant path. Multi-tenant providers vary the issuer per tenant and this catches people out.
  • exp, nbf, iat — expiry, not-before, issued-at, all in Unix seconds.
  • sub — the stable user identifier. Key your user records on this, not on email, which changes.
  • scope or scp — the granted scopes, which may be fewer than you asked for.
  • nonce — on an ID token, must match the nonce you sent on the authorization request.
  • alg and kid in the header — the signing algorithm and which key from the provider's JWKS signed it.

The decoder flags a missing alg, an alg of none (the classic signature-stripping vulnerability, and never legitimate in a real deployment), a token already past exp, a token not yet valid per nbf, a missing exp entirely, and a lifetime longer than a year.

It does not verify the signature, and this is deliberate rather than a shortcoming to work around: verification requires the shared secret for HMAC algorithms or the issuer's public key for RSA and ECDSA, and pasting a production signing secret into a web page is not something you should do anywhere. Decoding tells you what a token claims. Only your backend, fetching the provider's JWKS and validating against it, tells you whether the token is genuine. Treat everything you read here as unverified input.

redirect_uri mismatch

The most common OAuth failure, and the least informative error. Providers match redirect URIs by exact string comparison — no normalisation, no wildcards, no leniency — because any flexibility here becomes an open-redirect vulnerability that leaks authorization codes.

The validator takes what you registered and what you actually sent and diffs them component by component: scheme, host, port, path, query and fragment. Ports are normalised to their scheme defaults so https://x.com and https://x.com:443 compare as equal, which is how providers generally treat them. Everything else is compared literally.

What it catches, in rough order of frequency:

  • A trailing slash. /callback and /callback/ are different URIs. Flagged explicitly because it is invisible when you read it.
  • http versus https. Often a dev registration used against a staging deployment that terminates TLS.
  • A port that appears on one side only, typically a dev server on 3000 or 5173.
  • Path case. /Callback is not /callback.
  • A query string on one side. Some providers reject any query parameters on a redirect URI outright.
  • www or a subdomain difference between the registration and the deployed host.

Two things to know beyond the diff. First, redirect_uri must be identical on the authorization request and the token request — sending it correctly on the first and omitting or altering it on the second produces invalid_grant at the token endpoint, which sends people hunting in entirely the wrong place. Second, register every environment's URI up front. Adding one at 2am during an incident means waiting on a console you may not have access to.

invalid_grant, and the other codes

The error debugger holds the standard OAuth 2.0 error codes with their HTTP status, their probable causes ranked by likelihood, and concrete fixes. invalid_grant deserves calling out because it is the most overloaded:

  • The code expired. Authorization codes are short-lived — on the order of minutes. A code that sat in a debugger while you inspected it is dead by the time you send it.
  • The code was already used. Single-use, always. React strict mode double-invoking an effect, a retried request, or a user refreshing the callback page will burn it and the second attempt fails.
  • redirect_uri differs between the authorization request and the token request, as above.
  • The refresh token was revoked or rotated. With rotation enabled, using an old refresh token after a newer one was issued is treated as replay, and providers commonly revoke the whole chain in response.

Also covered: invalid_client (credentials or the wrong client authentication method — HTTP Basic versus form post is a frequent silent mismatch), invalid_request, unauthorized_client (grant type not enabled for that client), access_denied (the user pressed cancel — handle it as a normal path, not an error page), unsupported_response_type, and invalid_scope.

Scope is not audience

These get confused constantly and the symptom is a 403 from an API against a token that decodes perfectly.

Scope is what the client asked permission to do: openid profile email, read:orders. It is a space-separated list on the authorization request, and the user may consent to a subset. Audience is which resource the resulting token is for. Many providers will happily issue you a token with all the scopes you requested and an audience pointing at their own userinfo endpoint rather than your API, unless you explicitly ask for your API — via an audience or resource parameter, or by using scopes prefixed with the API's identifier, depending on the provider.

The diagnostic is direct: decode the access token and look at aud. If it is not your API's identifier, no amount of scope tuning will help. Add the audience parameter to the authorization request in the Flow Tester and try again.

Related: requesting a scope the client is not authorised for yields invalid_scope, and requesting one the user declines yields a token with fewer scopes than you asked for and no error at all. Always read the granted scope claim back rather than assuming you got what you requested.

Clock skew

Token validation is time-based, so a wrong clock breaks it in ways that look like anything but a clock problem. A validator rejects a token as expired when exp is in its past, or as not-yet-valid when nbf is in its future. If your server's clock runs a couple of minutes ahead of the identity provider's, freshly issued tokens are "expired" on arrival.

  • Symptom to recognise: intermittent or total 401s with a valid-looking token, often on one host in a pool and not others.
  • Standard practice is a small leeway — commonly a minute or two — when comparing exp, nbf and iat. Most JWT libraries expose this; use it rather than widening token lifetimes.
  • Fix the underlying cause with NTP on every host that validates tokens, including containers and CI runners.
  • Note that this decoder evaluates expiry against your own device's clock. If your laptop clock is wrong, its expiry verdict will be wrong too — a useful thing to rule out before you go looking at the provider.

A working order for debugging

  • Build the authorization URL in the Flow Tester with the parameters your app actually sends. If the provider rejects it, the problem is in the request, before any code exists.
  • If you get redirected back with an error, run the two redirect URIs through the validator before anything else.
  • If you got a code but the token exchange fails, look up the exact error code — and check that the redirect URI is identical on both requests.
  • If you got tokens but the API refuses them, decode the access token and read aud, iss, exp and scope, in that order.
  • If everything decodes correctly and it still fails, compare your clock against the issuer's, then check the signature on your backend against the provider's JWKS.

Debug OAuth and OIDC Flows

Test OAuth 2.0 authorization flows and decode OIDC tokens. Essential for authentication troubleshooting.

Supported Flows

  • Authorization Code (with PKCE)
  • Client Credentials
  • Implicit (legacy)
  • Device Code

Token Analysis

Decode access tokens and ID tokens. Verify signatures, check claims, validate expiration.

How JWT Decoding Works

JWT Token Structure

A JWT token consists of three parts separated by dots (.):

  1. Header - Contains metadata about the token type and signing algorithm
  2. Payload - Contains claims (statements about the user and additional data)
  3. Signature - Used to verify the token hasn't been tampered with

Decoding Process

Each part is Base64URL encoded. To decode a JWT:

  1. Split the token by the dot (.) separator
  2. Base64URL decode each part
  3. Parse the header and payload as JSON
  4. The signature remains as a Base64URL string

Important Security Note

Decoding a JWT does not verify its signature. Anyone can decode a JWT and read its contents. Always verify the signature server-side before trusting the claims in a token.

PKCE Implementation Guide

Implementing PKCE in Your Application

PKCE (Proof Key for Code Exchange) adds security to the OAuth authorization code flow. Here's how to implement it:

Step 1: Generate Code Verifier

Create a cryptographically random string (43-128 characters):

  • Use a secure random number generator
  • Encode as Base64URL
  • Store securely in your application

Step 2: Create Code Challenge

Compute the SHA-256 hash of the code verifier and encode it as Base64URL:

code_challenge = BASE64URL(SHA256(code_verifier))

Step 3: Authorization Request

Include these parameters in your authorization URL:

  • code_challenge - The computed challenge
  • code_challenge_method - Set to "S256"

Step 4: Token Exchange

When exchanging the authorization code for tokens, include:

  • code_verifier - The original verifier (NOT the challenge)

The authorization server will verify that SHA256(code_verifier) matches the code_challenge from the authorization request.

Common OAuth Error Solutions

Troubleshooting OAuth Errors

invalid_grant

Most Common Causes:

  1. Authorization code expired (typically >10 minutes)
  2. Code was already used (single-use only)
  3. Redirect URI mismatch

Solutions:

  • Ensure code exchange happens quickly
  • Never reuse authorization codes
  • Use exact redirect_uri in both requests

invalid_client

Most Common Causes:

  1. Wrong client_id or client_secret
  2. Client authentication method mismatch

Solutions:

  • Verify credentials match your provider dashboard
  • Check if using correct auth method (basic, post, jwt)

unauthorized_client

Most Common Causes:

  1. Grant type not enabled for client
  2. Redirect URI not registered

Solutions:

  • Enable required grant types in provider settings
  • Register all redirect URIs (exact match required)

access_denied

Most Common Causes:

  1. User clicked "Deny" on consent screen
  2. User lacks required permissions

Solutions:

  • Handle denial gracefully in your app
  • Request only necessary scopes
  • Provide clear explanation of why permissions are needed

OAuth Security Best Practices

Securing Your OAuth Implementation

1. Always Use PKCE

PKCE is no longer just for public clients. Use it for all OAuth flows to prevent authorization code interception attacks.

2. Short-Lived Access Tokens

Keep access tokens short-lived (5-15 minutes). Use refresh tokens for longer sessions.

3. Validate State Parameter

Always generate a unique state parameter for each authorization request and validate it on callback. This prevents CSRF attacks.

4. Exact Redirect URI Matching

Never use wildcards or regex for redirect URIs. OAuth requires exact string matching for security.

5. Verify Token Signatures

Always verify JWT signatures server-side before trusting token contents. Don't skip this step!

6. Check Token Expiration

Validate the exp claim and reject expired tokens. Don't accept tokens without expiration.

7. Secure Token Storage

  • Never store tokens in localStorage (vulnerable to XSS)
  • Use httpOnly cookies for refresh tokens
  • Store access tokens in memory when possible

8. Use HTTPS Everywhere

Never use OAuth over unencrypted HTTP in production. All endpoints must use HTTPS.

9. Implement Token Rotation

Rotate refresh tokens on each use to limit exposure if compromised.

10. Limit Scope Permissions

Request only the minimum scopes your application needs. Follow the principle of least privilege.

Need Help Implementing OAuth/OIDC?

Our security experts can help you implement secure OAuth 2.0 and OpenID Connect authentication in your applications. We provide architecture review, implementation guidance, and security audits for your authentication systems.

Frequently Asked Questions

How do I decode a JWT token?+

Paste your JWT token into the "JWT Decoder" tab. The tool will automatically decode the header, payload, and signature. You'll see all claims with syntax highlighting, validation status, and expiration warnings. Optionally provide a secret to verify the signature. All decoding happens in your browser - tokens are never sent to any server.

What is PKCE and why should I use it?+

PKCE (Proof Key for Code Exchange) is a security extension to OAuth 2.0 that prevents authorization code interception attacks. It's required for mobile and single-page applications (SPAs) and recommended for all OAuth clients. Use the "PKCE Generator" tab to create a code verifier and code challenge. Send the challenge during authorization and the verifier during token exchange.

How do I test an OAuth authorization flow?+

Go to the "Flow Tester" tab, select your OAuth flow type (Authorization Code, PKCE, etc.), fill in the required parameters (client_id, redirect_uri, scope), and click "Generate Authorization URL". Copy the URL and open it in a new tab to start the OAuth flow. The tool will help you understand each step and provide examples for token exchange.

Why is my redirect URI validation failing?+

OAuth requires exact string matching for redirect URIs. Common issues include: trailing slash differences (/callback vs /callback/), protocol mismatches (http vs https), port numbers (localhost:3000 vs localhost:8080), and query parameters. Use the "Redirect Validator" tab to compare your URIs character-by-character and see exactly what's different.

What does "invalid_grant" error mean?+

'invalid_grant' is the most common OAuth error with multiple possible causes. The top three are: (1) authorization code expired (usually >10 minutes old), (2) code was already used (codes are single-use), and (3) redirect_uri mismatch between authorization and token requests. Use the "Error Debugger" tab for detailed troubleshooting steps ranked by likelihood.

Is it safe to paste production tokens into this tool?+

Yes - all token processing happens entirely in your browser using JavaScript. No tokens, secrets, or sensitive data are sent to any server or stored permanently. The tool uses browser sessionStorage (cleared when you close the tab) and never logs data to analytics. However, always be cautious with production secrets and use development/test tokens when possible.

How do I verify JWT signatures? Why doesn't this tool verify them?+

JWT signature verification requires access to the secret key (for HMAC algorithms like HS256) or the public key (for asymmetric algorithms like RS256/ES256). Since this is a client-side tool and we never store, transmit, or have access to your signing keys, we cannot verify signatures. This tool is designed for inspecting and understanding JWT structure, debugging claims, and analyzing algorithms - not for production authentication. Always verify JWT signatures server-side using proper cryptographic libraries before trusting the token contents.

What OAuth flows should I use for my application?+

For server-side web apps: Authorization Code Flow (with client secret). For mobile apps and SPAs: Authorization Code Flow with PKCE. For backend service-to-service: Client Credentials Flow. Avoid legacy flows: Implicit Flow and Resource Owner Password Flow are deprecated due to security concerns. See the "Flow Tester" tab for detailed descriptions.

How do I generate a secure PKCE code verifier and challenge?+

Go to the "PKCE Generator" tab and click "Generate New PKCE Pair". The tool creates a cryptographically random code verifier (256 bits of entropy) and computes the code challenge using SHA-256 (S256 method, recommended) or plain method. Copy the challenge for your authorization request and keep the verifier for token exchange.

What are common JWT security vulnerabilities I should know about?+

Major JWT vulnerabilities include: (1) Algorithm confusion attacks - changing "alg" to "none" or switching from RS256 to HS256 to forge tokens. (2) Weak signing secrets - short or common secrets that can be brute-forced. (3) Missing signature verification - accepting tokens without validating signatures. (4) Missing expiration claims - tokens that never expire, allowing indefinite access if stolen. (5) Token storage issues - storing JWTs in localStorage where XSS attacks can steal them. (6) Accepting untrusted algorithms - not enforcing algorithm allowlists. Always use strong algorithms (RS256, ES256), verify signatures, include expiration claims, and store tokens securely.

When should I use JWT vs traditional session tokens?+

Use JWTs when: You need stateless authentication across distributed systems, APIs, or microservices; implementing single sign-on (SSO); mobile apps need offline access; scaling horizontally without shared session storage. Use session tokens when: You need instant token revocation (logout); managing long-lived sessions; security is paramount (JWTs can't be invalidated once issued until expiration); working with traditional server-rendered apps. JWTs are self-contained and stateless (don't require server-side session storage), while session tokens require a session store but offer better revocation control.

What's the difference between HS256 and RS256 algorithms?+

HS256 (HMAC-SHA256) is a symmetric algorithm where the same secret key is used for both signing and verifying tokens. This means anyone who can verify tokens can also create them, making key distribution risky. RS256 (RSA-SHA256) is an asymmetric algorithm where a private key signs tokens and a separate public key verifies them. Only the authorization server with the private key can create tokens, but any service can verify tokens using the widely-distributed public key. RS256 is recommended for distributed systems, while HS256 may be acceptable for simple, single-server applications where the secret never leaves the server.

How do JWT expiration and refresh tokens work together?+

JWT access tokens should have short lifespans (5-15 minutes) to limit exposure if stolen. When the access token expires, applications use a separate refresh token (stored securely, often in httpOnly cookies) to obtain a new access token without requiring the user to log in again. Refresh tokens have longer lifespans (days to weeks) and are used only with the authorization server, never sent to resource APIs. This two-token pattern balances security (short-lived access) with user experience (no constant re-authentication). The refresh token can be revoked server-side for instant logout, while access tokens remain stateless.

Related tools

This tool is provided for informational and educational purposes only. All processing happens in your browser — no data is sent to or stored on our servers. While we strive for accuracy, we make no warranties about the completeness or reliability of results.