jwt-cpp is a JWT authentication library that handles sensitive cryptographic operations and identity assertions. This document outlines security considerations, best practices, and the library's security design.
If you discover a security vulnerability, please report it privately:
- Do not open a public GitHub issue
- Email the maintainer with details about the vulnerability
- Include steps to reproduce, potential impact, and suggested fixes if available
- Allow reasonable time for a fix before public disclosure
Ed25519 Signatures (via nkeys-cpp)
- Implementation: nkeys-cpp wrapping Monocypher 4.x
- Algorithm: Ed25519 (Curve25519 + SHA-512)
- Key size: 256-bit (32 bytes)
- Signature size: 512-bit (64 bytes)
- Security level: 128-bit (equivalent to AES-128)
JWT Algorithm
- Header:
{"typ": "JWT", "alg": "ed25519-nkey"} - Signature: Ed25519 over
header.payload(Base64 URL encoded) - Encoding: Base64 URL without padding (RFC 4648 §5)
Claim Validation
- Subject/Issuer verification
- Expiration timestamp checking (
expfield) - Issued-at timestamp validation (
iatfield) - Trust hierarchy enforcement (Operator → Account → User)
- Required field validation
Signature Verification
- Always verify signature before trusting claims
- Constant-time comparison (via nkeys-cpp)
- Key type validation (User JWT signed by Account key, etc.)
Sensitive Data Handling
JWT tokens themselves are public (signed, not encrypted), but the library must protect:
- Seeds/Private Keys: Handled by nkeys-cpp with automatic wiping
- Temporary Buffers: Wiped after encoding/decoding operations
- RAII Guards: Exception-safe cleanup of sensitive data
Delegation to nkeys-cpp
This library does not implement cryptographic primitives. All sensitive operations (key generation, signing, verification) are delegated to nkeys-cpp, which provides:
- Automatic memory wiping
- Exception-safe key handling
- Secure random number generation
All public APIs perform strict validation:
- JWT Format: Must be
header.payload.signatureformat - Base64 Encoding: Valid Base64 URL characters only
- JSON Payloads: Valid JSON structure required
- Claim Fields: Required fields must be present
- Key Types: Issuer key type must match claim type
- Signature Length: Must be exactly 64 bytes
- Maximum Size: JWT limited to 10MB (configurable via
MAX_JWT_SIZE)
Invalid input results in exceptions, never undefined behavior.
DO:
- ✅ Set appropriate expiration times on JWTs
- ✅ Verify signatures before trusting claims
- ✅ Check expiration timestamps before accepting tokens
- ✅ Validate issuer/subject relationships (trust hierarchy)
- ✅ Store seeds securely (use nkeys-cpp best practices)
- ✅ Transmit JWTs over secure channels (TLS/HTTPS)
- ✅ Revoke compromised tokens (via NATS infrastructure)
DON'T:
- ❌ Accept expired tokens
- ❌ Skip signature verification
- ❌ Trust claims without validating issuer
- ❌ Store sensitive data in JWT payload (JWTs are signed, not encrypted)
- ❌ Use infinite expiration (
exp = 0) in production - ❌ Reuse operator/account signing keys carelessly
Critical Verification Steps:
// 1. Decode JWT (this verifies signature automatically)
try {
auto claims = jwt::decodeUserClaims(token);
// 2. Check expiration
if (claims->expires() != 0 && claims->expires() < currentTime()) {
throw std::runtime_error("Token expired");
}
// 3. Validate issuer (must be trusted account key)
if (!isTrustedAccount(claims->issuer())) {
throw std::runtime_error("Untrusted issuer");
}
// 4. Use claims safely
std::cout << "Authenticated user: " << claims->subject() << "\n";
} catch (const std::exception& e) {
// Handle verification failure
std::cerr << "JWT verification failed: " << e.what() << "\n";
}Never:
- Skip signature verification
- Accept tokens without checking expiration
- Trust issuer without validation
- Use JWT payload as encrypted data
Operator → Account → User
Each level can only sign JWTs for the level below:
Operator (self-signed)
└─> Account JWT (signed by operator key or operator signing key)
└─> User JWT (signed by account key or account signing key)
Validation Rules:
- Operator JWT:
subject == issuer(self-signed) - Account JWT:
issuermust be operator key - User JWT:
issuermust be account key - Signing keys: Must be in parent's
signing_keyslist
All encoding/decoding functions use exception-safe patterns:
try {
std::string jwt = claims.encode(seed);
// Even if exception thrown, nkeys-cpp has wiped seed
} catch (const std::exception& e) {
// Handle error safely
}When built with JWT_ENABLE_HARDENING=ON (default), the following protections are enabled:
Stack Protection
-fstack-protector-strong: Guards stack against buffer overflows
Fortified Sources
-D_FORTIFY_SOURCE=2: Buffer overflow checks (Release builds)
Position Independent Execution (Linux)
-Wl,-z,relro,-z,now: Read-only relocations, prevents GOT overwrites
Development builds can enable:
AddressSanitizer (-DJWT_ENABLE_ASAN=ON)
- Detects memory errors (use-after-free, buffer overflows)
- ~2x slowdown, use in testing
UndefinedBehaviorSanitizer (-DJWT_ENABLE_UBSAN=ON)
- Detects undefined behavior at runtime
- Minimal performance impact
- Cryptography: Depends on nkeys-cpp platform support
- macOS/Linux: Fully supported
- Windows: Limited by nkeys-cpp RNG support
JWTs are Signed, Not Encrypted
- Payload is visible to anyone (Base64 encoded)
- Signature proves authenticity, not confidentiality
- Never put secrets in JWT payload
Expiration is Advisory
- Token expiration enforced by verifier, not cryptographically
- Compromised token valid until expiration
- No built-in revocation (use NATS revocation mechanisms)
The library does not protect against:
- Excessive JWT size (enforced by
MAX_JWT_SIZEconstant) - CPU exhaustion from signature verification
- Memory exhaustion from large claim sets
The library protects against:
- ✅ JWT forgery (Ed25519 signature security)
- ✅ Token tampering (signature verification)
- ✅ Key confusion (prefix validation via nkeys-cpp)
- ✅ Timing attacks (constant-time signature verification)
- ✅ Trust hierarchy violations (issuer validation)
- ✅ Expired token acceptance (expiration checking)
The library does NOT protect against:
- ❌ Compromised private keys (key management is user responsibility)
- ❌ Token theft (use TLS/HTTPS for transmission)
- ❌ Replay attacks (application-level concern, use nonces if needed)
- ❌ Token revocation (use NATS revocation lists)
- ❌ Payload confidentiality (JWTs are signed, not encrypted)
- ❌ Physical access to running process
- ❌ Root/admin level attackers
Ed25519 is considered secure against all known attacks:
- No known practical attacks against Curve25519
- Conservative security margin
- Immune to many side-channel attacks
- Widely peer-reviewed and deployed
Not Quantum-Resistant: Ed25519 is vulnerable to quantum computers with Shor's algorithm.
- Uses nkeys-cpp for all cryptographic operations
- nkeys-cpp uses Monocypher, an audited library
- No custom cryptographic code ("don't roll your own crypto")
This library is suitable for:
- General-purpose authentication
- Internal service-to-service authentication
- NATS-based microservices
- Developer tooling and automation
This library is NOT certified for:
- FIPS 140-2/140-3 compliance
- Medical device software
- Payments (PCI-DSS)
- Government classified systems
Always consult security/compliance experts for regulated environments.
Before deploying jwt-cpp in production:
- Seeds stored securely (use nkeys-cpp best practices)
- Build includes hardening flags (
JWT_ENABLE_HARDENING=ON) - Signature verification never skipped
- Expiration timestamps checked on all tokens
- Issuer validation enforces trust hierarchy
- JWTs transmitted only over secure channels (TLS/HTTPS)
- No sensitive data in JWT payload
- Token expiration times set appropriately (not infinite)
- Tested with sanitizers during development
- Exception handling reviewed for security
- Security incident response plan in place
- Regular dependency updates (nkeys-cpp, nlohmann/json)
Threat: Attacker intercepts JWT during transmission
Mitigation:
- Use TLS/HTTPS for all JWT transmission
- Use short expiration times
- Implement token revocation at application level
Threat: Attacker reuses captured JWT
Mitigation:
- Use short expiration times
- Implement nonce/jti validation if needed
- Monitor for suspicious patterns
Threat: Application accepts expired tokens
Mitigation:
- Always check
expires()field before accepting token - Reject tokens with
exp< current time - Use reasonable clock skew tolerance (e.g., 5 minutes)
Threat: User token signed by untrusted account
Mitigation:
- Validate
issuerfield matches trusted account - Check account JWT was signed by trusted operator
- Maintain whitelist of trusted operator keys
- Monitor this repository for security updates
- Subscribe to GitHub releases for notifications
- Review nkeys-cpp releases for cryptographic updates
- Review nlohmann/json releases for parsing vulnerabilities
- Keep compiler and standard library updated