For the complete documentation index, see llms.txt. This page is also available as Markdown.

Security Best Practices

Production hardening checklist for bx-jwt — allowlists, key sizes, clock skew, and common pitfalls.

JWTs are easy to use but easy to misuse. This page collects the production hardening guidance specific to bx-jwt — most are one-line configuration changes.

1. Reject alg:none (Built-in)

bx-jwt unconditionally rejects unsigned (alg:none) tokens. There is no setting to disable this. Any attempt to verify or refresh such a token throws bxjwt.JWTVerificationException.

2. Lock Down the Algorithm Allowlist

Algorithm-confusion attacks rely on the verifier accepting an algorithm the issuer never intended (the classic example is asking an RSA verifier to treat a public key as an HMAC secret). Cut the attack off by enumerating exactly the algorithms you expect:

// ModuleConfig.bx
allowedAlgorithms = [ "HS256", "RS256" ]

Any token signed with anything else throws JWTVerificationException. Leaving the array empty allows every algorithm the module supports — fine for prototypes, not for production.

3. Use Compliant HMAC Key Sizes

RFC 7518 §3.2 defines minimum HMAC key lengths and bx-jwt enforces them at parse time:

Algorithm
Minimum

HS256

32 bytes (256 bits)

HS384

48 bytes (384 bits)

HS512

64 bytes (512 bits)

Use jwtGenerateSecret( bits ) to always produce a compliant key. Hand-typed strings almost always fail this check.

4. Tune Clock Skew Per Environment

clockSkew (default: 60 seconds) absorbs drift between distributed services without opening a large vulnerability window. Tune it down for security-sensitive flows, up for far-flung clusters:

5. Always Assert Issuer / Audience

A token signed by auth-service for the analytics audience should not authenticate against the payments API. Pin both:

Or set defaultIssuer / defaultAudience in ModuleConfig.bx so they are auto-injected on issue.

6. Keep Lifetimes Short

  • Short-lived access tokens (minutes to ~1 hour) limit the blast radius of theft.

  • Pair with a refresh flow using jwtRefresh() and allowExpired: true for graceful renewal.

  • Always set exp — either explicitly in the payload, with .expireIn() on the builder, or via the defaultExpiration setting.

7. Stamp kid and Verify Through It

For any deployment that may rotate keys (i.e. all of them), stamp a kid header on every signed token and look the key up at verify time. See Key Rotation for the full pattern.

8. Never Trust jwtDecode() Output

jwtDecode() is read-only — it does not verify signatures. Use it to pick a kid or detect the alg, then always follow up with jwtVerify() before acting on any claim.

9. Encrypt PII / PHI With JWE

For payloads containing sensitive personal data, use JWE instead of (or in addition to) JWS. For both integrity and confidentiality, sign first then encrypt — the nested JWT pattern.

10. Store Keys Outside the Codebase

Use the keys registry with ${env.VAR} substitution, or load keys from a KMS / Vault at runtime via JWTService.registerKey(). Keys committed to source control should be considered compromised.

Quick Checklist

Last updated

Was this helpful?