Developer Tools

Free JWT Generator Online

Create and sign JWT tokens with HS256, HS384 or HS512. Set custom claims and expiry.

JWT Generator: How JWTs Work and How to Use Them Securely

JSON Web Tokens (JWTs) are the most widely used token format for stateless authentication in modern web applications. From REST API authorization to single sign-on (SSO) systems, JWTs appear in virtually every production backend. This guide explains the specification, the signing algorithms, the security properties, and the common pitfalls that cause production incidents.

Anatomy of a JWT

A JWT is a string of three Base64url-encoded segments separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE0MDAwMDAwLCJleHAiOjE3MTQwMDM2MDB9.abc123...

Part 1 — Header: A JSON object specifying the algorithm (alg) and token type (typ). Base64url-decoding the first segment above gives:

{ "alg": "HS256", "typ": "JWT" }

Part 2 — Payload: A JSON object containing claims — statements about the entity (typically a user) and additional data. Base64url-decoding gives:

{
  "sub": "1234567890",
  "name": "Alice",
  "iat": 1714000000,
  "exp": 1714003600
}

Part 3 — Signature: The HMAC (or RSA/ECDSA) signature over base64url(header).base64url(payload) using the secret key. The signature ensures the token has not been tampered with.

The key point: the header and payload are not encrypted — they are merely encoded. Anyone who receives the token can read the payload. The signature only proves authenticity (the token was created by someone with the secret) and integrity (the payload has not been modified). Never put sensitive data in the payload.

Standard Claims (RFC 7519 Section 4)

The JWT specification defines a set of registered claim names. All are optional, but each serves a specific purpose:

ClaimNameTypeDescription
issIssuerStringWho created the token (e.g. "https://auth.example.com")
subSubjectStringWho the token is about (e.g. user ID "u-1234")
audAudienceString/ArrayWho should accept the token (e.g. "api.example.com")
expExpirationNumericDateUnix timestamp after which the token is invalid
nbfNot BeforeNumericDateUnix timestamp before which the token must not be accepted
iatIssued AtNumericDateUnix timestamp when the token was issued
jtiJWT IDStringUnique identifier; used to prevent replay attacks

Minimum viable JWT for authentication: include sub (who is authenticated), iat (when issued), and exp (when it expires). Always include exp — tokens without expiry are valid indefinitely, meaning a leaked token provides permanent access.

Signing Algorithms: HS256, HS384, HS512

HMAC-SHA algorithms use a single shared secret key for both signing and verification. The number indicates the SHA hash length used internally:

  • HS256 — HMAC with SHA-256, 256-bit output. Most common; supported by all JWT libraries. Use this unless you have specific requirements.
  • HS384 — HMAC with SHA-384, 384-bit output. Marginally more collision-resistant; rarely used.
  • HS512 — HMAC with SHA-512, 512-bit output. Longest signature; best for high-security applications but the added security over HS256 is negligible for HMAC.

For HMAC algorithms, the bottleneck is key length, not hash size. A 256-bit secret with HS256 provides the same security as a 256-bit secret with HS512. The difference only matters if your secret is shorter than the hash output length (which it should not be in production).

Asymmetric alternatives (not implemented in this tool, but important to know):

  • RS256 — RSA with SHA-256. Sign with a private key; verify with a public key. Essential for distributed systems where multiple services need to verify tokens without access to the signing secret.
  • ES256 — ECDSA with SHA-256 and P-256 curve. Shorter signatures than RSA, faster verification. Preferred over RS256 for new systems.
  • EdDSA — Ed25519 signatures. Fastest and most secure asymmetric option; supported by modern libraries.

Secret Key Requirements

For HS256, your secret should be at least 256 bits (32 bytes) of random data. This matches the SHA-256 output size. A shorter key is technically valid per the spec but provides less security — a 64-bit key means only 2^64 possible keys, vulnerable to offline brute force.

Generating a secure secret (use one of these approaches):

# Using OpenSSL (32 bytes = 256 bits, hex encoded)
openssl rand -hex 32

# Using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# Using Python
python3 -c "import secrets; print(secrets.token_hex(32))"

# Using a password manager (e.g. 1Password) — generate a random 64-character string

Store the secret in environment variables (JWT_SECRET), a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler), or a key management service (AWS KMS, Google Cloud KMS). Never hardcode it in source code.

Implementing JWT Verification (Server Side)

Generating a JWT is only half the story. Verification on the server is where security is enforced. Always use a well-maintained library rather than implementing verification yourself:

// Node.js — jsonwebtoken library
import jwt from 'jsonwebtoken';

// Verification (always verify before trusting claims)
try {
  const payload = jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],  // explicitly allowlist algorithms
    audience:   'api.example.com',
    issuer:     'https://auth.example.com',
  });
  console.log(payload.sub); // trusted user ID
} catch (err) {
  // TokenExpiredError, JsonWebTokenError, NotBeforeError
  return res.status(401).json({ error: 'Invalid token' });
}
# Python — PyJWT library
import jwt

try:
    payload = jwt.decode(
        token,
        secret,
        algorithms=["HS256"],
        audience="api.example.com",
        issuer="https://auth.example.com",
    )
    user_id = payload["sub"]
except jwt.ExpiredSignatureError:
    raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
    raise HTTPException(status_code=401, detail="Invalid token")

The "alg: none" Vulnerability

One of the most infamous JWT security vulnerabilities: some early implementations accepted tokens with "alg": "none" in the header — meaning no signature was required. An attacker could modify the payload and set alg to none to bypass signature verification entirely.

Mitigation: always explicitly specify which algorithms your verification code accepts, as shown in the examples above (algorithms: ['HS256']). Never use a library that defaults to accepting alg: none.

Access Tokens vs. Refresh Tokens

Production authentication systems use a two-token pattern:

  • Access token: Short-lived (15 minutes to 1 hour). Sent with every API request. If leaked, the damage window is limited to the token's remaining lifetime. Typically a JWT stored in memory (not persisted).
  • Refresh token: Long-lived (7–30 days). Stored securely (HttpOnly cookie). Used only to obtain new access tokens when the current one expires. Not sent with every API request — only to the token endpoint. If leaked, it can be revoked server-side by invalidating its record in the database.

This pattern is sometimes called the "sliding session" or "silent refresh" pattern. The user appears to stay logged in indefinitely (because access tokens are silently refreshed), but the server can log out any user at any time by deleting their refresh token record.

JWT Storage: Cookies vs. localStorage

The right storage mechanism depends on your threat model:

StorageXSS resistantCSRF resistantRecommended for
HttpOnly + Secure cookieYes (JS can't read it)No (needs SameSite or CSRF token)Web apps
SameSite=Strict cookieYesYes (same-site only)Web apps (same origin)
localStorageNo (XSS can steal it)Yes (not sent automatically)Not recommended
Memory (JS variable)Yes (not persisted)YesSPAs with refresh tokens in cookies

The current industry consensus: use HttpOnly, Secure, SameSite=Strict cookies for the refresh token, and keep the access token in JavaScript memory only (lost on page refresh, refreshed silently). This prevents both XSS theft and CSRF attacks.

Token Revocation

A pure JWT system has no built-in revocation mechanism — once a token is issued, it's valid until it expires. To implement revocation:

  • Short access token lifetimes: If tokens expire in 15 minutes, a revoked user regains access for at most 15 minutes.
  • Token blocklist: Store revoked JTI (JWT ID) values in Redis until their exp. Check the blocklist on each request. Adds one Redis lookup per request.
  • Token versioning: Store a token_version field in the user record. Include the version in the JWT. If the stored version increments (on password change, logout, forced logout), existing tokens with the old version are rejected.
  • Refresh token revocation: Deleting a refresh token record effectively logs out the user at next access token expiry. Combined with short access token lifetimes, this is the most practical approach.

FAQ

Common questions

What is a JWT and what is it used for?

A JSON Web Token (JWT) is a compact, URL-safe token format defined in RFC 7519. It contains three Base64url-encoded parts: a header (algorithm + type), a payload (claims), and a signature. JWTs are commonly used for stateless authentication — a server signs a token containing the user's identity and permissions, then the client presents that token on every subsequent request without the server needing to query a session store.

What is the difference between HS256, HS384, and HS512?

All three are HMAC-based symmetric algorithms defined in RFC 7518. The number refers to the SHA hash length used: 256, 384, or 512 bits. HS256 is the most common and sufficient for most applications. HS384 and HS512 produce longer signatures and are marginally more collision-resistant, but the practical security difference is negligible for HMAC — the bottleneck is key length, not hash size. Use HS256 unless your requirements specify otherwise.

What claims should every JWT include?

The most important standard claims (defined in RFC 7519 Section 4.1) are: iss (issuer — who created the token), sub (subject — who the token represents), aud (audience — who the token is intended for), exp (expiration time — Unix timestamp after which the token is invalid), iat (issued-at time), and jti (JWT ID — unique identifier to prevent replay). At minimum, always include sub and exp to limit the blast radius if a token is leaked.

Is a JWT encrypted?

No — a JWT signed with HS256/384/512 is only signed, not encrypted. The header and payload are Base64url-encoded (reversible), so anyone who intercepts the token can read its contents. Never put sensitive data (passwords, credit card numbers, SSNs) in a JWT payload. If you need confidentiality, use JWE (JSON Web Encryption) or a different mechanism.

What is the difference between signing and encrypting a JWT?

Signing (JWS) proves that the token was created by someone who knows the secret key and that it has not been modified. Anyone can read the payload — signing does not hide it. Encrypting (JWE) hides the payload so only the intended recipient can read it. Most applications use JWS (signed JWTs) for authentication; JWE is less common and requires more infrastructure.

Should I store JWTs in localStorage or cookies?

Both have tradeoffs. localStorage is accessible to JavaScript, making JWTs vulnerable to XSS attacks. HttpOnly cookies are immune to JavaScript access, preventing XSS theft, but require CSRF protection (e.g. SameSite=Strict or a CSRF token). The current industry consensus is HttpOnly cookies with SameSite=Strict for web apps. For mobile or API clients, short-lived JWTs in memory (not persisted) are preferred.

How long should a JWT's expiry (exp) be?

Keep access token lifetimes short — 15 minutes to 1 hour is common. Short expiry limits damage if a token is leaked. Use a separate, longer-lived refresh token (7–30 days) to obtain new access tokens without requiring the user to log in again. Never issue non-expiring JWTs for production authentication flows.

Is my secret key safe when using this tool?

Yes. All signing happens locally in your browser using the Web Crypto API. Your secret key and payload are never sent to any server, never stored, and never logged. You can verify this by going offline — the generator works identically without an internet connection.

More in Developer Tools