A JWT signing key is security-critical material. If an attacker obtains a symmetric key, they may be able to create valid-looking tokens for every verifier that trusts it. If a private asymmetric key leaks, the same issue applies to every relying party that accepts its public key. Generate keys locally or through an approved key-management workflow, keep them out of source control and logs, and design verification as a policy rather than a decoder call.

Key Takeaways

  • JWT is a claims container; JWS provides integrity/authentication of the signed representation, while JWE provides encryption. Base64URL is encoding, not confidentiality.
  • Generate HMAC keys from a CSPRNG. 32 bytes is not the same thing as 32 characters; encoding changes representation, not entropy.
  • Pin an algorithm allowlist and validate iss, aud, exp, nbf, iat, typ, and application-specific claims according to the trust contract.
  • A valid signature proves possession of the configured verification key, not that the subject is authorized for a tenant, object, or action.
  • Treat kid, issuer metadata, and JWKS URLs as untrusted selectors. Resolve keys from an allowlisted, pinned, monitored source; never fetch arbitrary URLs during verification.
  • Rotate with an overlap bounded by token lifetime, support revocation or session invalidation, and record key IDs and verification outcomes without logging tokens or secrets.

JWT, JWS, and Encryption

A compact signed JWT commonly uses JWS compact serialization:

text
base64url(header) + "." + base64url(payload) + "." + base64url(signature)

For HS256, the signature is an HMAC over the encoded header and payload using a shared secret. The signature detects changes when verification is correctly configured; it does not make the payload private and does not independently establish authorization.

Do not call an HMAC secret an encryption key. Do not treat claims such as role, tenant_id, or scope as trustworthy until the token has passed signature, issuer, audience, time, and policy checks. Authorization still belongs to the service handling the requested resource.

Algorithm and Key Choices

Algorithm family Key material Verification distribution Typical consideration
HS256/HS384/HS512 One shared HMAC secret Every verifier knows the secret Simple, but any verifier can also sign
RS256/PS256 RSA private/public key pair Verifiers receive public key Useful when signing must be isolated
ES256 EC private/public key pair Verifiers receive public key Smaller signatures; curve and library support matter
EdDSA Ed25519 or another supported EdDSA key Verifiers receive public key Modern option when all libraries support it

Choose from the actual trust boundary and library support. Switching from HS256 to RS256 is not a drop-in security upgrade: key formats, verification configuration, claims, deployment, and rotation must all be tested.

HMAC Secret Length and Entropy

For HMAC JWT algorithms, use at least the algorithm's stated minimum key size:

Algorithm Minimum random material
HS256 32 bytes (256 bits)
HS384 48 bytes (384 bits)
HS512 64 bytes (512 bits)

These are bytes of random material, not a recommendation to type a 32-character password. Hex encodes one byte as two characters; unpadded Base64URL uses about four characters for every three bytes. Do not use human passwords, application names, UUID text, timestamps, hashes of predictable strings, or passphrases as JWT HMAC keys.

Generate Keys Locally

Use an operating-system-backed CSPRNG and avoid printing production secrets into shared terminals, CI logs, shell history, crash reports, or chat:

bash
# 32 random bytes for HS256; store through an approved secret workflow.
openssl rand -base64 32

# 64 random bytes for HS512.
openssl rand -base64 64

# Hex representation of 32 random bytes.
openssl rand -hex 32

Base64 may include +, /, and =; use a secret-store value format that preserves them. Base64URL without padding can be more convenient for environment transport, but it does not add entropy.

Node.js

javascript
import { randomBytes } from "node:crypto";

export function generateHmacSecret(byteLength = 32) {
  if (![32, 48, 64].includes(byteLength)) {
    throw new RangeError("use 32, 48, or 64 random bytes");
  }
  return randomBytes(byteLength).toString("base64url");
}

Python

python
import base64
import secrets


def generate_hmac_secret(byte_length: int = 32) -> str:
    if byte_length not in {32, 48, 64}:
        raise ValueError("use 32, 48, or 64 random bytes")
    return base64.urlsafe_b64encode(secrets.token_bytes(byte_length)).rstrip(b"=").decode()

Go

go
package keygen

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
)

func GenerateHMACSecret(byteLength int) (string, error) {
	if byteLength != 32 && byteLength != 48 && byteLength != 64 {
		return "", fmt.Errorf("use 32, 48, or 64 random bytes")
	}
	key := make([]byte, byteLength)
	if _, err := rand.Read(key); err != nil {
		return "", fmt.Errorf("generate key: %w", err)
	}
	return base64.RawURLEncoding.EncodeToString(key), nil
}

The examples generate material; they do not store it. Write the result directly to an approved secret manager or KMS workflow, apply access controls, and prevent the value from being echoed by automation.

Verification Policy

A decoder should not decide trust from the token's header. Configure the verifier with a fixed algorithm set and an expected issuer and audience:

javascript
import jwt from "jsonwebtoken";

export function verifyAccessToken(token, key) {
  return jwt.verify(token, key, {
    algorithms: ["HS256"],
    issuer: "https://issuer.example",
    audience: "api.example",
    // Keep clock tolerance small and explicit in the deployment policy.
    clockTolerance: 5,
  });
}

The exact options vary by library. Verify exp and nbf with bounded clock skew, require the claims your contract needs, and reject unexpected token types. Do not accept alg: none, infer an HMAC secret from an RSA public key, or allow a token to choose an untrusted verification algorithm.

After cryptographic verification, authorize the principal for the tenant, object, fields, and action. A valid sub or scope claim is not a substitute for a current authorization decision.

Secure Storage and Separation

Store signing material in an approved secrets manager, KMS, HSM, or equivalent control plane. Limit read access to the services that need it, audit access, and separate development, test, staging, and production keys. Use distinct keys for access tokens, refresh tokens, email links, password resets, and unrelated MAC purposes.

Avoid putting secrets in .env files that enter source control, container layers, images, debug dumps, telemetry, or browser bundles. Environment variables can be acceptable as an injection mechanism only when the deployment platform protects their lifecycle and access.

Rotation, kid, and Revocation

A rotation plan needs explicit states:

  1. publish the new public key or provision the new symmetric key to verifiers;
  2. switch signers to the new key and include a stable kid;
  3. verify both keys during a bounded overlap;
  4. stop accepting the old key after the maximum token lifetime, revocation event, or incident policy;
  5. remove and destroy the old key according to retention requirements.

For symmetric keys, every verifier must receive both secrets, which increases blast radius. For asymmetric keys, publish public keys through an authenticated, allowlisted JWKS or configuration channel. kid is only a lookup hint; reject unknown IDs, prevent key-type confusion, and never turn it into an arbitrary URL or file path.

JWTs are often bearer credentials and may remain valid until expiry. If immediate invalidation matters, maintain server-side session state, token families, a revocation list, or a key-level emergency plan. Rotation alone is not universal revocation.

Common Mistakes

Mistake Risk Control
Using a short password or app name Offline guessing and token forgery Generate random bytes with a CSPRNG
Counting characters as bits Less entropy than intended Measure random bytes before encoding
Trusting the token's alg Algorithm confusion or downgrade Fixed server-side allowlist
Fetching any JWKS URL from kid/iss SSRF and key substitution Allowlisted, pinned metadata sources
Treating Base64URL as encryption Sensitive claims are exposed Minimize claims; use a justified JWE design
Using one key for every purpose/environment Broad compromise scope Separate keys and access policies
Logging tokens or generated secrets Credential disclosure Redact and bound audit data
Retrying old keys forever Revoked tokens remain accepted Bound overlap and enforce expiry/revocation

Incident Response

If a signing key may have leaked:

  1. mark the key compromised and stop issuing with it;
  2. deploy a new key and update verifiers through the controlled channel;
  3. reject the compromised kid or revoke affected sessions;
  4. invalidate refresh-token families and sensitive sessions as required;
  5. investigate source-control, logs, CI, host, dependency, and access records;
  6. preserve evidence and document the affected issuer, audience, time window, and token classes.

Do not claim that changing a key deletes already copied tokens or undoes external side effects. Incident containment and authorization review remain necessary.

Frequently Asked Questions

How many random bytes are needed for HS256, HS384, and HS512?

Use at least 32, 48, and 64 random bytes respectively, subject to the algorithm and library guidance. Count entropy before Base64 or hexadecimal encoding; printable character length is not the same measure.

Can a UUID be used as an HMAC secret?

Do not use UUID text as a default JWT secret. Its entropy and generation properties may not meet the algorithm requirement, and its recognizable format encourages misuse. Generate key material directly with a CSPRNG.

Is Base64 or hex more secure?

Neither encoding adds security. Both can represent the same random bytes; choose a representation that the secret store and configuration path preserve without truncation or escaping errors.

How often should keys rotate?

There is no universal 30- or 90-day rule. Base the overlap and rotation schedule on token lifetime, compromise detection, issuer population, operational recovery, compliance, and the cost of re-authentication. Rotate immediately when compromise is suspected.

Does signing a JWT encrypt it?

No. JWS signing provides integrity and signer-key authentication; the payload remains readable. Use encryption only with a separately designed JWE threat model and key lifecycle.

Primary Sources

Conclusion

Secure JWT operation begins with correctly generated and controlled signing keys, but it does not end there. Select the algorithm for the trust boundary, verify a fixed policy and required claims, keep authorization outside the token, protect key material throughout its lifecycle, and make rotation and incident response testable. Never use an online generator or a convenient string as a substitute for a documented key-management process.