Help us improve
Share bugs, ideas, or general feedback.
From grimoire
Implements JWT authentication with algorithm pinning, short expiry, and server-side validation to prevent token forgery and algorithm confusion attacks.
npx claudepluginhub jeffreytse/grimoire --plugin grimoireHow this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-jwt-securityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Issue JWTs with explicit algorithm pinning, short expiry, and server-side validation — protecting against algorithm confusion attacks, token forgery, and indefinite token validity.
Implements secure JWT signing with HS256, RS256, ES256, EdDSA; verifies signatures, claims, expiration; defends against algorithm confusion, none alg, key injection attacks.
Implements secure JWT signing and verification with HMAC-SHA256, RSA-PSS, EdDSA, and defense against algorithm confusion, none algorithm, and key injection attacks.
Implements secure JWT signing with HS256, RS256, ES256, EdDSA and verification in Python, including expiration, claims validation, key rotation, and defenses against algorithm confusion, none alg, key injection.
Share bugs, ideas, or general feedback.
Issue JWTs with explicit algorithm pinning, short expiry, and server-side validation — protecting against algorithm confusion attacks, token forgery, and indefinite token validity.
Adopted by: RFC 8725 "JWT Best Current Practices" (IETF, 2020) is the definitive standard. Auth0, Okta, AWS Cognito, and Google Identity Platform all enforce strict JWT validation. OWASP API Security Top 10 2023 (API2:Broken Authentication) cites JWT misuse. PCI DSS v4.0 Requirement 8.6 requires authentication token expiration controls.
Impact: The alg:none attack (CVE-2015-9235 in jwt.js) allowed any JWT to be accepted as valid by setting the algorithm to "none" — effectively bypassing all authentication. The RS256/HS256 algorithm confusion attack (exploited at Accenture, SecureWorks, and in CTF competitions) allows attackers to forge tokens using the public key as the HMAC secret. Auth0 paid $1,500–$5,000 bounties for JWT misuse findings. Improper JWT validation is a top finding in API security audits.
Why best: Client-side storage of authentication state (JWT) vs. server-side sessions — JWTs eliminate database lookups but require strict validation. The tradeoff: JWTs cannot be revoked without a denylist, so short expiry is mandatory. Opaque session tokens (see design-session-management) are better for applications needing instant revocation; JWTs are better for stateless, distributed systems.
Sources: RFC 7519 (JWT); RFC 8725 (JWT Best Current Practices); IETF; CWE-347; Auth0 security blog
Pin the algorithm explicitly on verification — never accept the algorithm from the token header:
import jwt # PyJWT
# BAD — accepts whatever alg the token claims
payload = jwt.decode(token, key, algorithms=None)
# GOOD — pin to one algorithm
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
# For RS256 (asymmetric):
payload = jwt.decode(token, PUBLIC_KEY, algorithms=['RS256'])
// Node.js — jsonwebtoken
// BAD
jwt.verify(token, key);
// GOOD
jwt.verify(token, key, { algorithms: ['RS256'] });
Use RS256 (asymmetric) for public APIs, HS256 for internal services:
Set short exp (expiry) claims — minutes for access tokens, hours for refresh tokens:
from datetime import datetime, timedelta, timezone
import jwt
payload = {
'sub': str(user_id),
'iat': datetime.now(timezone.utc),
'exp': datetime.now(timezone.utc) + timedelta(minutes=15), # access token
'jti': str(uuid.uuid4()), # unique token ID for revocation
}
token = jwt.encode(payload, PRIVATE_KEY, algorithm='RS256')
Typical values: access token 15 min, refresh token 7 days. Never issue tokens without exp.
Validate all required claims — exp, iss (issuer), aud (audience), nbf (not before):
payload = jwt.decode(
token,
PUBLIC_KEY,
algorithms=['RS256'],
options={
'require': ['exp', 'iss', 'sub'],
'verify_exp': True,
'verify_iss': True,
},
issuer='https://auth.example.com',
audience='https://api.example.com',
)
Implement token revocation with a denylist for logout and high-security actions:
# Store revoked JTIs in Redis with TTL = token remaining expiry
def revoke_token(jti, ttl_seconds):
redis.setex(f'revoked:{jti}', ttl_seconds, '1')
def is_revoked(jti):
return redis.exists(f'revoked:{jti}')
# On every request:
payload = jwt.decode(token, ...)
if is_revoked(payload['jti']):
raise AuthError("Token revoked")
Store JWTs securely on the client side:
HttpOnly cookie: protected from XSS, vulnerable to CSRF (mitigate with SameSite=Lax + CSRF token).localStorage: accessible to JavaScript, vulnerable to XSS token theft.HttpOnly + SameSite=Lax cookie is the right choice.Rotate signing keys periodically — support multiple valid keys during rotation:
# Publish a JWKS endpoint for key discovery
# kid (key ID) in JWT header identifies which key was used for signing
# Keep old key active for existing tokens' remaining lifetime during rotation
base64.decode(token.split('.')[1]) is not verification.exp must be validated server-side — a library that doesn't check exp by default is a security risk.none must always be rejected — any library that accepts it should be replaced or wrapped.alg from the token header — the foundation of algorithm confusion attacks; always pin explicitly.aud claim — a token issued for service A accepted by service B because aud isn't checked is a privilege escalation.