mrkeyoor.com_
Sun 20 Sept 11:44 UTC
npmSecurityupdated 20 Sept 2026

jwt-decode review

jwt-decode 4.0.0 Base64URL-decodes the payload or header of a JSON Web Token and parses that segment as JSON. A browser can use it to show a claim, inspect exp before asking for a refresh, or read kid before trusted verification code selects a key. It does not validate the signature, issuer, audience, expiry, or authorization policy. Version 4 uses a named export, targets Node 18 or newer, removes its atob polyfill, and publishes separate ESM and CommonJS paths.

Verdict

jwt-decode 4.0.0 added 0 dependencies and only 0.6 KB gzipped in our browser build, with 0 audit findings, but every decoded claim remains unverified. Install it for display or refresh hints; use a JWT verifier whenever the result influences identity, trust, or access.

We installed it

Lab card: what happened when we installed jwt-decodeScreenshot of jwt-decode documentation
Install✓ · 0.6s1 package on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser0.6 KBgzipped (1.1 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does jwt-decode install cleanly?

Yes. In a fresh container with an empty cache, npm install jwt-decode finished in 0.6s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does jwt-decode add to a browser bundle?

0.6 KB gzipped (1.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does jwt-decode work with both ESM and CommonJS?

Yes. Both import 'jwt-decode' and require('jwt-decode') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does jwt-decode include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

jwt-decode or jose: which should you use?

jose: Use it for JWT verification, signing, remote JWKS, or JWE across Web Crypto runtimes. jwt-decode 4.0.0 added 0 dependencies and only 0.6 KB gzipped in our browser build, with 0 audit findings, but every decoded claim remains unverified.

When should you not use jwt-decode?

A decoded field can grant access; the README states that any well-formed JWT can be decoded, including one an attacker created

How do you import jwtDecode in jwt-decode v4?

Auth0's jwt-decode v4 exposes jwtDecode as a named export, so import it with braces rather than using the old default-import syntax.

import { jwtDecode } from "jwt-decode";

const claims = jwtDecode(token);
console.log(claims.sub, claims.exp);

For CommonJS, destructure the same named export:

const { jwtDecode } = require("jwt-decode");

Version 4 changed jwtDecode from a default export to a named export. Replace import jwtDecode from "jwt-decode" when migrating older examples.

The decisive caveat is that jwt-decode only decodes a well-formed JWT. It does not validate the token or verify its signature. Reading claims can be useful for client-side display logic, but any server-side authentication or authorization decision must use verified token data.

API stability4/5The package exposes one main function, one header option, standard claim interfaces, and InvalidTokenError. Version 4 still made several breaking packaging changes: jwtDecode became a named export, Node 14 and 16 support ended, the internal atob fallback disappeared, and conditional exports split ESM from CommonJS. The API has been unchanged since that 2023 release.
Docs4/5The README puts its no-validation warning before the usage example. It then covers payload and header decoding, custom TypeScript claims, CommonJS, a browser module, old React Native polyfills, and each malformed-input error. That is enough for the small API, though the general Auth0 documentation link does not provide a deeper package reference.
Maintenance3/5npm's current 4.0.0 release was published on 2023-10-27. The repository is not archived and was pushed on 2026-08-20, with 3,395 stars and 17 open issues and pull requests when inspected. Repository activity shows continued ownership, but consumers have not received a package release for nearly 3 years.
Ecosystem5/5npm recorded 18,753,786 downloads from 2026-08-19 through 2026-08-25. The package supplies typed ESM and CommonJS entry points, runs in current browsers and supported Node releases, and documents the older React Native atob gap. It has no plugin system because decoding is its only operation; verification belongs to packages such as jose.

Use it if

  • Client UI needs to display claims from a token whose authority is enforced elsewhere
  • A browser reads exp only to decide when it should request a fresh token
  • Verification code inspects kid before choosing a key from a trusted JWKS
  • You need one typed decode function and explicitly do not need signing, verification, or encryption
Skip it if

Setup reality

We installed jwt-decode 4.0.0 in 0.6 seconds under Node 22. One package occupied 1 MB, and npm audit found 0 vulnerabilities at all 4 severity levels. The package itself is 52 KB unpacked, declares 0 direct and 0 peer dependencies, includes TypeScript declarations, and uses the MIT license. Our esbuild check measured 1.1 KB minified and 0.6 KB gzipped for a browser import.

The package has type=module and an exports map. Both ESM import and require() worked in our sandbox. Version 4 exposes the named jwtDecode export, so a default import copied from version 3 code must change. No issuer URL, key, credential, or config file is requested because decoding has no trust step. The header option selects segment 1; the default selects the payload in segment 2.

jwtDecode calls the global atob function. Node 18 and current browsers provide it, while React Native before 0.74 needs a startup polyfill. InvalidTokenError reports a non-string input, a missing segment, invalid Base64URL, or invalid JSON. Check browser storage for null before calling it and catch malformed tokens at storage or network boundaries.

Claims remain untrusted after successful parsing. A TypeScript generic describes the expected result to the compiler and does not check the runtime object. An exp-based browser timer is only a refresh hint because suspended tabs and sleeping devices delay callbacks. Recalculate after resume, and verify the signature plus registered claims on a trusted server with jose or another verifier.

Patterns

Read standard payload claims decode-payload

import { jwtDecode } from 'jwt-decode';

const claims = jwtDecode(accessToken);
console.log(claims.sub, claims.exp);

Parsing does not prove who issued the token. Treat sub and exp as untrusted until verification succeeds.

Read the key identifier decode-header

import { jwtDecode } from 'jwt-decode';

const header = jwtDecode(token, { header: true });
console.log(header.kid, header.alg);

kid can locate a key in a trusted set. Do not allow the unverified alg field to weaken the verifier's configured policy.

Describe private claims in TypeScript type-custom-claims

import { jwtDecode, type JwtPayload } from 'jwt-decode';

interface Claims extends JwtPayload {
  email?: string;
  'https://example.com/roles'?: string[];
}
const claims = jwtDecode<Claims>(token);

The generic changes the static return type only. It performs 0 runtime checks on values or array elements.

Handle malformed token text catch-invalid-token

import { InvalidTokenError, jwtDecode } from 'jwt-decode';

function decodeOrNull(raw) {
  try { return jwtDecode(raw); }
  catch (error) {
    if (error instanceof InvalidTokenError) return null;
    throw error;
  }
}

InvalidTokenError covers the wrong input type, a missing selected segment, invalid Base64URL, and invalid JSON.

Check storage before parsing guard-storage

const token = sessionStorage.getItem('access_token');
const claims = token === null ? null : jwtDecode(token);

getItem returns null for an absent key. jwtDecode accepts strings, so passing that null value raises InvalidTokenError.

Decide whether to request refresh hint-expiry

function expiresSoon(token, marginSeconds = 30) {
  const { exp } = jwtDecode(token);
  if (typeof exp !== 'number') return true;
  return exp <= Math.floor(Date.now() / 1000) + marginSeconds;
}

JWT NumericDate uses seconds and Date.now() uses milliseconds. The trusted API must still enforce the verified expiry.

Handle both aud shapes normalize-audience

const { aud } = jwtDecode(token);
const audiences = Array.isArray(aud)
  ? aud
  : typeof aud === 'string'
    ? [aud]
    : [];

JwtPayload defines aud as a string or string array. Normalizing it for display does not establish that your service is an intended audience.

Set a client refresh timer schedule-refresh

function scheduleRefresh(token, refresh) {
  const { exp } = jwtDecode(token);
  if (typeof exp !== 'number') return () => {};
  const delay = Math.max(0, exp * 1000 - Date.now() - 60_000);
  const id = setTimeout(refresh, delay);
  return () => clearTimeout(id);
}

A background tab may wake after the calculated time. Recheck exp on focus or app resume instead of trusting 1 timer.

Load the CommonJS named export require-commonjs

const { jwtDecode } = require('jwt-decode');
const claims = jwtDecode(token);

Version 4's require result is an exports object. Destructure jwtDecode rather than calling the require result directly.

Support React Native before 0.74 polyfill-atob

import 'core-js/stable/atob';
import { jwtDecode } from 'jwt-decode';

const claims = jwtDecode(token);

Load the polyfill before the first decode. Node 18 or newer and current browsers already define atob.

Verify a token with remote keys verify-jose

import { createRemoteJWKSet, jwtVerify } from 'jose';

const jwks = createRemoteJWKSet(
  new URL('https://issuer.example/.well-known/jwks.json'),
);
const { payload } = await jwtVerify(token, jwks, {
  issuer: 'https://issuer.example/',
  audience: 'reports-api',
});

Use verification like this when claims affect authorization. jwt-decode cannot establish the signature, issuer, or audience.

Return an explicit parse result decode-safe-result

function inspectToken(raw) {
  if (typeof raw !== 'string') return { ok: false, reason: 'missing' };
  try {
    return { ok: true, claims: jwtDecode(raw) };
  } catch (error) {
    return { ok: false, reason: 'malformed' };
  }
}

The ok flag means the JSON parsed. It does not mean the JWT is valid, current, or accepted by any issuer.

Alternatives

PackageRegistryPick it when
josenpmUse it for JWT verification, signing, remote JWKS, or JWE across Web Crypto runtimes
jsonwebtokennpmUse it in a Node service that signs and verifies JWTs with configured secrets or public keys
jwt-simplenpmUse it only for a small legacy Node flow that already owns its algorithm and key policy

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.