JWT Decoder
What's inside this token?
Anyone holding a JSON Web Token can read it: the header and claims are only base64-encoded, not encrypted. Paste one here to decode it, see when it expires, check its signature, and find out whether it's a Supabase anon key, a user session, or a service-role key that should never leave your server.
Decoded in your browser. The token is never uploaded. A live token works like a password until it expires, so prefer expired or test tokens.
What this JWT decoder shows
Header and payload
Both halves decoded into formatted JSON, with every claim listed and the standard ones explained.
Expiry and timing
exp, iat, and nbf turned into real dates, with a clear expired, not-yet-valid, or never-expires status.
Signature verification
HS, RS, PS, and ES algorithms, checked against your secret or a PEM or JWK public key with your browser's built-in crypto.
Supabase key detection
Tells an anon key from a user session token and from a service-role key that bypasses Row Level Security.
Read more →Forgery risks
alg: none, tokens that name their own signing key through jku, x5u, or jwk, and tokens that never expire.
Read more →Data that shouldn't be there
Claims named like passwords, secrets, or API keys, which anyone holding the token can read.
How the JWT Decoder works
- Step 1
Paste a token
Anything starting with eyJ, with or without Bearer in front. It's decoded as you paste, in your browser.
- Step 2
Read the claims and warnings
The decoded header and payload, what each claim means, when the token expires, and any security problems with how it's built.
- Step 3
Verify it if you need to
Add the signing secret or public key to confirm the token is genuine and unchanged. The key stays on your machine too.
Frequently asked questions
How do I decode a JWT?
A JWT is three base64url strings joined by dots: the header, the payload, and the signature. Decoding means base64url-decoding the first two and reading the JSON, which is why anyone can do it without a key. Paste the token above, or in JavaScript split it on the dots and run JSON.parse on atob of the second part after swapping - for + and _ for /. Decoding shows what a token claims; only verifying the signature tells you whether to believe it.
Is it safe to paste a JWT into a website?
Only if the site decodes it locally. This one does: decoding and verification run in your browser with the Web Crypto API, and the token, secret, and key are never sent to Ubserve, stored, or put in the URL. You can confirm it in the Network tab of DevTools. Even so, a live access token works like a password until it expires, so use expired or test tokens when you can, and never paste a private key anywhere.
What's the difference between decoding and verifying a JWT?
Decoding reads the claims, and anyone can do it. Verifying checks the signature against the secret or public key the token was signed with, which proves it came from whoever holds that key and hasn't been edited since. A server must always verify before trusting a claim, and must reject alg: none. Otherwise a user can change their own role or user ID and re-encode the token.
How do I tell a Supabase anon key from a service-role key?
Decode the key and read its role claim. anon is the public key that's meant to ship in your frontend; service_role is the admin key that bypasses Row Level Security on every table; a signed-in user's session token says authenticated. If a service_role key has ever been in client code, a public repo, or a shared chat, rotate it in your Supabase project settings. Newer Supabase keys start with sb_publishable_ or sb_secret_ and aren't JWTs, but the same public-versus-secret rule applies.
Is it free? Do I need to sign in?
Yes. It's free, with no account, no sign-in, and no usage limit. Ubserve makes money from its paid security report, not from this page. The full scan it pairs with is free to run too: you get a grade, an issue count, and up to two findings in full before anything is asked of you.
Why does my JWT say it's expired?
The exp claim is a Unix timestamp in seconds, and a token is rejected once the current time passes it. Access tokens are meant to be short-lived: Supabase issues them for an hour by default and refreshes them with a refresh token. If a token looks expired the moment it's issued, check the server's clock, and check that your code isn't comparing exp, which is in seconds, against Date.now(), which is in milliseconds.
What founders say
5.0 out of 5 - Ubserve, from 3 reviews
“Helped me find a critical database issue that would've done real damage. I recommend it for every founder.”
“Caught a critical CSP issue on my frontend within minutes. Don't ship without a scan.”
“The free scan took under 60 seconds to find real issues in my project.”
Other free tools
See all tools →Exposed API Key Scanner
Downloads your deployed JavaScript and matches it against key patterns for 30+ providers, from Stripe to OpenAI to AWS.
Open tool →Supabase RLS Checker
Probes your Supabase tables with the anon key from your own bundle to see which ones answer without Row Level Security.
Open tool →Secret Scanner
Paste code, a .env file, or config and find API keys and credentials before you commit. Runs entirely in your browser.
Open tool →Keep reading
- JWT token forgery and claim spoofingHow attackers get a server to accept a token they wrote.
- Supabase service-role key exposureThe key that bypasses every policy you write.
- Broken access control in AI-built appsWhen a valid token still reaches the wrong data.
- Supabase RLS not working? Here's whyWhen a policy exists but protects nothing.
- I exposed an API key - what now?The rotation order that limits the damage.
Check what your live app serves
Anything you check here only covers what you hand it. What an attacker sees is what your deployed app serves, so paste your URL and check that too.