How to Decode a JWT Token in JavaScript (No Libraries)
A JWT's header and payload are Base64URL-encoded JSON — meaning you can read them with nothing but atob() and JSON.parse(). This guide shows the full decode function, how to handle expiration, Node.js compatibility, and when to reach for a library.
The Base64URL Decoding Approach
A JWT has three parts separated by dots: header.payload.signature. The header and payload are Base64URL-encoded JSON. Base64URL is almost identical to Base64, but uses - instead of + and _ instead of / (URL-safe characters), and omits padding = characters.
To decode it with the browser's atob(), you need to:
- Replace
-with+ - Replace
_with/ - Add padding
==if needed (length must be a multiple of 4) - Call
atob()to decode - Call
JSON.parse()on the result
Full decodeJwt() Function
Handling Expiration (exp Claim)
The exp claim is a Unix timestamp in seconds (not milliseconds). Compare it with Date.now() / 1000:
atob vs Buffer in Node.js
atob() was added to Node.js in v16. For older Node.js environments, use Buffer:
TypeScript Typed Version
Verifying vs Decoding — When to Use a Library
Manual decoding is fine for reading a JWT — inspecting claims, checking expiration display, debugging. But if you need to trust the claims and use them to authorize access, you need to verify the signature.
| Use case | Approach |
|---|---|
| Debugging — read payload in browser devtools | Manual decode (this guide) |
| Display user name/email from token in UI | Manual decode (safe, display-only) |
| Server-side auth middleware | Library: jose or jsonwebtoken |
| Verify token before granting access | Library: jose or jsonwebtoken |
| Generate signed tokens | Library: jose or jsonwebtoken |
Related Tools
Related Guides
FAQ
Why can I decode a JWT without a library?
A JWT's header and payload are simply Base64URL-encoded JSON strings. Base64URL is a variant of Base64 that uses - instead of + and _ instead of /. Since browsers have a built-in atob() function for Base64 decoding, you can decode a JWT with just a few lines of vanilla JavaScript.
Does decoding a JWT verify its signature?
No. Decoding only reads the payload — it does not verify that the token was signed by the expected server. Never trust the claims in a decoded JWT without server-side signature verification. Decoding is useful for debugging and inspecting claims, but is not a security measure.
What is the difference between atob() and Buffer in Node.js?
atob() is the browser's built-in Base64 decoder, available in browsers and Node.js 16+. In older Node.js versions, use Buffer.from(str, 'base64').toString('utf8'). For Base64URL (JWT encoding), replace - with + and _ with / before decoding with either method.
How do I check if a JWT is expired?
Decode the payload and read the exp claim, which is a Unix timestamp (seconds since epoch). Compare it with Date.now() / 1000. If exp < Date.now() / 1000, the token is expired.
When should I use a library instead of manual decoding?
Use a library (like jose or jsonwebtoken) when you need to verify signatures, not just decode. Signature verification requires cryptographic operations and knowledge of the algorithm and key. For read-only decoding of non-sensitive tokens during debugging, manual decoding is perfectly fine.