OAuth Token Decoder
Decode OAuth 2.0 access tokens in JWT format. Understand the claims and structure of tokens from OAuth providers like Auth0, Okta, and Azure AD.
OAuth Token Types
| Token Type | Format | Purpose |
|---|---|---|
| Access Token | JWT or opaque | API authorization |
| ID Token | Always JWT | User identity (OpenID Connect) |
| Refresh Token | Usually opaque | Obtain new access tokens |
Common OAuth JWT Claims
| Claim | Provider | Description |
|---|---|---|
| iss | All | OAuth server URL |
| sub | All | User identifier |
| aud | All | Client ID or API identifier |
| scope | Most | Granted permissions |
| client_id | Most | Application identifier |
| azp | Google, Keycloak | Authorized party |
| OIDC | User email | |
| name | OIDC | User display name |
OAuth Token Decoder
``javascript
function decodeOAuthToken(token) {
const parts = token.split('.');
// Check if opaque token
if (parts.length !== 3) {
return {
type: 'opaque',
note: 'This is an opaque token - cannot be decoded client-side',
token: token.substring(0, 20) + '...'
};
}
// Decode JWT
const decode = (s) => JSON.parse(atob(s.replace(/-/g, '+').replace(/_/g, '/')));
const header = decode(parts[0]);
const payload = decode(parts[1]);
// Identify provider
const provider = identifyProvider(payload.iss);
// Analyze scopes
const scopes = payload.scope ? payload.scope.split(' ') : [];
return {
type: 'jwt',
provider,
header,
payload,
scopes,
audience: Array.isArray(payload.aud) ? payload.aud : [payload.aud],
expiresAt: payload.exp ? new Date(payload.exp * 1000) : null
};
}
function identifyProvider(issuer) {
if (!issuer) return 'unknown';
if (issuer.includes('auth0.com')) return 'Auth0';
if (issuer.includes('okta.com')) return 'Okta';
if (issuer.includes('login.microsoftonline.com')) return 'Azure AD';
if (issuer.includes('accounts.google.com')) return 'Google';
if (issuer.includes('cognito-idp')) return 'AWS Cognito';
return 'custom';
}
`
Provider-Specific Claims
| Provider | Unique Claims |
|---|---|
| Auth0 | permissions, org_id |
| Azure AD | oid, tid, upn |
| hd (hosted domain), azp | |
| Okta | groups, cid |
| Cognito | cognito:groups, cognito:username |
The Attacks a Verifier Must Stop
alg: none. A token declaring no algorithm with an empty signature. A library that
trusts the header's alg will accept it as valid. Always specify the expected algorithm
when verifying rather than reading it from the token.
Algorithm confusion. A token signed with HMAC using the server's *public* RSA key as the
secret. If the verifier picks the algorithm from the header, an RS256 verifier can be tricked
into running HS256 with a key the attacker already has.
Weak secrets. HS256 with a short or dictionary secret is brute-forceable offline from a
single captured token. Use at least 256 bits of real entropy.
No expiry check. exp is a claim, not an enforcement. A library that decodes without
verifying, or code that reads claims from a decoded-but-unverified token, accepts expired and
forged tokens alike.
`javascript
// Specify the algorithm; never trust the header's
jwt.verify(token, secret, { algorithms: ['HS256'] });
`
Anyone Can Read the Payload
A JWT is signed, not encrypted. The payload is base64url — readable by anyone holding the
token, including the browser it is stored in. Never put anything in it you would not print
on a postcard: no passwords, no PII beyond an identifier, no internal keys.
Revocation Is the Hard Part
A signed token is valid until it expires, and there is no way to withdraw one. Options:
| Approach | Cost |
|---|---|
| Short expiry (5–15 min) plus refresh tokens | Standard; adds a refresh endpoint |
| A denylist of revoked JTIs | Reintroduces the state JWTs were meant to avoid |
| Rotate the signing key | Revokes every token at once |
| Version claim checked against the user record | A database read per request |
If you need immediate revocation for every session, a server-side session is a simpler and
more honest choice than a JWT.Where to Store One
| Location | XSS-safe | CSRF-safe |
|---|---|---|
localStorage | No | Yes |
| sessionStorage` | No | Yes |
| Cookie (HttpOnly, Secure, SameSite) | Yes | Yes, with SameSite |
| In-memory | Yes | Yes |
An HttpOnly cookie is the safest default. Any token reachable from JavaScript is reachable by
any script that gets injected.