Debug OAuth 2.0 and OpenID Connect flows. Decode JWTs, generate PKCE challenges, validate redirect URIs, and troubleshoot OAuth errors.
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.
| Tab | Input | What you get |
|---|---|---|
| JWT Decoder | An access token or ID token | Decoded header and payload, expiry in human terms, structural warnings |
| PKCE Generator | Nothing — press generate | A code verifier and its S256 challenge, ready to paste into a request |
| Flow Tester | Endpoint, client ID, redirect URI, scopes | A complete, correctly encoded authorization URL you can open |
| Redirect Validator | Registered URI and actual URI | A component-by-component diff showing exactly what differs |
| Error Debugger | An error code from your provider | Ranked probable causes and the fixes for each |
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:
code_challenge = base64url(SHA-256(verifier)) and code_challenge_method=S256 on the authorization request.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.
Conflating these causes more OIDC bugs than anything else, so be precise:
| Access token | ID token | Refresh token | |
|---|---|---|---|
| Answers | What may this client do? | Who signed in, and how? | May I have new tokens? |
| Audience | The API / resource server | Your client application | The authorization server |
| Who validates it | The API you call | Your client, once at sign-in | The token endpoint |
| Format | Opaque or JWT — provider's choice | Always a JWT (OIDC requires it) | Opaque, essentially always |
| Lifetime | Short — minutes to an hour | Short; it is a login receipt, not a session | Long, and often single-use with rotation |
Three rules that follow from that table and prevent most of the damage:
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.
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:
/callback and /callback/ are different URIs. Flagged explicitly because it is invisible when you read it./Callback is not /callback.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.
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:
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.
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.
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.
exp, nbf and iat. Most JWT libraries expose this; use it rather than widening token lifetimes.aud, iss, exp and scope, in that order.Test OAuth 2.0 authorization flows and decode OIDC tokens. Essential for authentication troubleshooting.
Decode access tokens and ID tokens. Verify signatures, check claims, validate expiration.
A JWT token consists of three parts separated by dots (.):
Each part is Base64URL encoded. To decode a JWT:
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 (Proof Key for Code Exchange) adds security to the OAuth authorization code flow. Here's how to implement it:
Create a cryptographically random string (43-128 characters):
Compute the SHA-256 hash of the code verifier and encode it as Base64URL:
code_challenge = BASE64URL(SHA256(code_verifier))
Include these parameters in your authorization URL:
code_challenge - The computed challengecode_challenge_method - Set to "S256"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.
Most Common Causes:
Solutions:
Most Common Causes:
Solutions:
Most Common Causes:
Solutions:
Most Common Causes:
Solutions:
PKCE is no longer just for public clients. Use it for all OAuth flows to prevent authorization code interception attacks.
Keep access tokens short-lived (5-15 minutes). Use refresh tokens for longer sessions.
Always generate a unique state parameter for each authorization request and validate it on callback. This prevents CSRF attacks.
Never use wildcards or regex for redirect URIs. OAuth requires exact string matching for security.
Always verify JWT signatures server-side before trusting token contents. Don't skip this step!
Validate the exp claim and reject expired tokens. Don't accept tokens without expiration.
Never use OAuth over unencrypted HTTP in production. All endpoints must use HTTPS.
Rotate refresh tokens on each use to limit exposure if compromised.
Request only the minimum scopes your application needs. Follow the principle of least privilege.
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.
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.
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.
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.
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.
'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.
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.
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.
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.
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.
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.
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.
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.
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.
Decode and inspect JWT tokens instantly. View header, payload, and verify signatures with security validation.
Encode and decode Base64 strings for data transport, email attachments, and web development
Format, validate, and beautify JSON data with syntax highlighting and error detection
Generate MD5, SHA-1, SHA-256, and SHA-512 hashes from text or files. HMAC support, multiple output formats, 100% client-side.
Check how strong your password is with entropy analysis, crack time estimates, and breach database checks via Have I Been Pwned. 100% client-side.