Decode a JWT in JavaScript and Node.js

Decode JWT tokens in JavaScript with jose (browser/ESM) and jsonwebtoken (Node.js). Read header and payload without verification. Code examples.

ZERO UPLOAD · ALL LOCAL
  1. Copy your JWT token — it looks like three Base64 sections separated by dots (eyJ…)
  2. Paste the token into the input field — decoding happens automatically on paste.
  3. Read the Header panel: it shows the signing algorithm (alg) and token type (typ).
  4. Read the Payload panel: it shows all claims — user ID, roles, issued-at (iat), and expiry (exp).
  5. Check the expiry status indicator — it shows whether the token is currently valid or expired, with a human-readable time delta.
  6. Note: the tool decodes only — it does not verify the signature. Never trust decoded claims in a security context without server-side verification.

What this page covers

  • jose decodeJwt() browser/ESM-friendly, no key required for reading claims
  • jsonwebtoken jwt.decode() Node.js only; pass { complete: true } to also read the header

HEADER
          
PAYLOAD
          
EXPIRY

Decode a JWT in JavaScript and Node.js

When debugging in JavaScript, the right decode path depends on where the code runs. jose is the modern ESM-compatible choice for browsers, Deno, and edge runtimes;1 jsonwebtoken is the established CommonJS library for Node.js backends.2 Both provide a decode-only path that returns raw payload claims without signature verification. The decoder above inspects any token offline in your browser: no library installation needed. For programmatic use in your application, the patterns below cover both libraries.

Pasting a JWT into a server-backed tool transmits the full token to a third party. Browser-based decoding eliminates this risk entirely. Because jose's decodeJwt() runs in the browser with zero network calls, your production tokens never leave your machine during debugging. For Node.js backends, jsonwebtoken's jwt.decode() with complete: true returns header, payload, and signature fields in one object: useful for building middleware that inspects claims before forwarding to a handler.

Library and setup

Install jose with npm install jose for browser and Deno projects, or use it in Node.js via ESM imports. For CommonJS Node.js code, install jsonwebtoken with npm install jsonwebtoken. jose exports named ESM functions: import { decodeJwt, decodeProtectedHeader } from 'jose': making tree-shaking effective in bundled applications.3 jsonwebtoken exports a default object: const jwt = require('jsonwebtoken'): following the CommonJS pattern.2 Both libraries are well-maintained; jose is the actively recommended choice for new projects, while jsonwebtoken remains widely deployed in existing codebases.

Choosing the runtime before the API

Pick the library by runtime first. Browser and edge code should use jose because it ships as ESM and avoids Node-specific APIs. Existing CommonJS backends can keep jsonwebtoken where it already works, but new ESM services should treat jose as the default. This prevents accidental imports that break bundling or serverless builds. A common mistake is importing jsonwebtoken into a Next.js edge middleware file, where Node.js-specific modules like crypto and buffer are unavailable at runtime, causing cryptic build failures that take significant time to trace back to the wrong library choice.

Decoding without verification

In jose, calling decodeJwt(token) returns the payload claims as a plain JavaScript object. For the header, call decodeProtectedHeader(token) to read alg, kid, and typ separately.3 In jsonwebtoken, jwt.decode(token, { complete: true }) returns an object with header, payload, and signature properties: the complete option is required to access the header.4 Neither call makes a network request or checks the signature. Consequently, both are safe for use during debugging and testing, but neither should gate access control decisions without a subsequent signature verification step, because a decoded payload without a verified signature is indistinguishable from one that an attacker constructed locally with arbitrary claims.

Reading specific claims

After decoding, access claims directly by property name. In jose, decodeJwt(token).sub gives the subject claim; decodeJwt(token).exp returns the expiry as a Unix timestamp in seconds. In jsonwebtoken's complete mode, payload.sub and payload.exp follow the same pattern. Building on this, the exp timestamp is always a number representing seconds since the Unix epoch, so you must multiply by 1000 to compare against Date.now() in milliseconds, which is the single most common cause of JWT expiry bugs in JavaScript applications.5 Custom claims from provider-specific fields like cognito:groups or realm_access appear in the decoded object using the same dot-notation access pattern. For nested objects, inspect the full payload before assuming a flat claim path.

Full signature verification with jose in Node.js

For full RS256 verification in Node.js, import jwtVerify and createRemoteJWKSet from jose. Create the JWKS resolver by calling createRemoteJWKSet with your provider's JWKS URL, then call jwtVerify(token, jwkSet, { issuer, audience }) to verify the signature and validate registered claims in one step. The function returns the decoded payload only on success; any failure throws an error you catch and translate into a 401 response.

JWKS caching and key rotation

Pass your expected issuer and audience strings to the jwtVerify options object. Providing these parameters removes the need for manual claim checking after verification. The jose library caches the JWKS response internally and refreshes automatically when it encounters an unknown kid, absorbing key rotation without any additional configuration in your middleware.6 During a rotation event, the library serves stale cached keys for existing tokens while fetching the new key set in the background, which means zero downtime for your API even when the provider rotates keys during peak traffic.

Keeping decode and verify paths separate

In middleware, keep the decode-only path separate from the verified path: decode the token first when you need to log claims or choose a tenant, then run jwtVerify before you trust the value. This split keeps debugging fast while preserving the security boundary that your API route must enforce, and keeping this helper near your auth middleware ensures future changes do not reintroduce decode-only access checks.

This separation also makes testing easier because you can exercise the decode path in unit tests without standing up a full verification backend. The verified result then flows into your existing authorization code that already expects a trusted token. Documenting the boundary next to the middleware prevents a future contributor from collapsing the two steps back into one insecure call. You can confirm a claim shape in decoding JWTs with jose or jsonwebtoken before writing either library's code.

When to use this

Use jose when building browser extensions, Next.js edge middleware, or Deno services that decode tokens client-side. Use jsonwebtoken when maintaining an existing Node.js codebase that already imports it. The decode-only approach matches jose's decodeJwt(), so you can confirm a claim shape in the browser before writing either library's code.

Examples

Decode JWT payload in the browser with jose

Before
import { decodeJwt, decodeProtectedHeader } from "jose";

const payload = decodeJwt(token);
console.log(payload.sub);
console.log(payload.exp);

const header = decodeProtectedHeader(token);
console.log(header.alg); // e.g. "RS256"
console.log(header.kid);

decodeJwt and decodeProtectedHeader never verify the signature. For verification, use jose's jwtVerify().

Decode JWT with header in Node.js (jsonwebtoken)

Before
const jwt = require("jsonwebtoken");

const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header.alg); // e.g. "RS256"
console.log(decoded.payload.sub);
console.log(decoded.payload.exp);

The complete: true option is required to access the header. Without it, jwt.decode returns only the payload.

Sources
  1. 1.

    npm, "jose 6.2.3," npmjs.com, accessed June 2026. https://www.npmjs.com/package/jose

  2. 2.

    auth0, "node-jsonwebtoken," github.com, accessed June 2026. https://github.com/auth0/node-jsonwebtoken

  3. 3.

    panva, "jose," github.com, accessed June 2026. https://github.com/panva/jose

  4. 4.

    auth0, "jsonwebtoken," npmjs.com, accessed June 2026. https://www.npmjs.com/package/jsonwebtoken

  5. 5.

    MDN, "Date.now() - JavaScript," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now

  6. 6.

    M. Jones et al., "JSON Web Token (JWT)," RFC 7519, IETF, May 2015. https://datatracker.ietf.org/doc/html/rfc7519

FAQ