JWT Key Persistence
JWT Signing Key Persistence
Section titled “JWT Signing Key Persistence”Overview
Section titled “Overview”By default, OctoFHIR generates a new JWT signing key pair on each server startup. This means all access tokens become invalid when the server restarts, requiring users to re-authenticate.
For production deployments, you should provide persistent JWT signing keys via configuration to maintain token validity across server restarts.
Signing Algorithm Selection
Section titled “Signing Algorithm Selection”OctoFHIR supports three JWT signing algorithms. Choose based on your requirements:
| Algorithm | Type | Key Size | Security | Verify Speed | SMART on FHIR | Recommendation |
|---|---|---|---|---|---|---|
| RS384 | RSA | 3072-bit | 128 bits | 🚀 Fast | ✅ Preferred | Recommended |
| RS256 | RSA | 2048-bit | 112 bits | 🚀 Fast | ✅ Compatible | Good for compatibility |
| ES384 | ECDSA | P-384 | 192 bits | 🐢 Slow | ✅ Preferred | Smaller tokens, slower |
Why RS384 is Recommended
Section titled “Why RS384 is Recommended”- Fast verification: RSA verification uses small public exponent (65537), making it ~10x faster than ECDSA
- SMART on FHIR compliant: RS384 is listed as a preferred algorithm in the SMART specification
- Sufficient security: 128-bit security level is more than adequate for short-lived JWT tokens
- Wide compatibility: Supported by all JWT libraries and clients
When to Use ES384
Section titled “When to Use ES384”- You need the smallest possible token size (EC signatures are ~96 bytes vs ~384 bytes for RSA)
- You have low request volume and verification speed doesn’t matter
- Your infrastructure already uses P-384 keys
Performance Impact
Section titled “Performance Impact”In high-throughput scenarios, ES384 can consume 20-30% of CPU time on signature verification alone. Switching to RS384 can significantly reduce this overhead.
Quick Start
Section titled “Quick Start”1. Generate RSA Keys (for RS256 or RS384) — Recommended
Section titled “1. Generate RSA Keys (for RS256 or RS384) — Recommended”# Generate a 3072-bit RSA private key (for RS384, provides 128-bit security)openssl genpkey -algorithm RSA -out jwt_private.pem -pkeyopt rsa_keygen_bits:3072
# Extract the public keyopenssl rsa -in jwt_private.pem -pubout -out jwt_public.pem
# For RS256, you can use 2048-bit keys (112-bit security, still secure)# openssl genpkey -algorithm RSA -out jwt_private.pem -pkeyopt rsa_keygen_bits:20482. Generate EC Keys (for ES384)
Section titled “2. Generate EC Keys (for ES384)”# Generate a P-384 EC private key (PKCS#8 format)openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out jwt_private.pem
# Extract the public keyopenssl ec -in jwt_private.pem -pubout -out jwt_public.pem3. Configure OctoFHIR
Section titled “3. Configure OctoFHIR”You have three options for providing the keys:
Option A: Direct Configuration File
Section titled “Option A: Direct Configuration File”Edit octofhir.toml:
[auth.signing]algorithm = "RS384"kid = "production-key-2024" # Optional but recommended
private_key_pem = """-----BEGIN PRIVATE KEY-----MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClWJYT...... (your full private key) ...-----END PRIVATE KEY-----"""
public_key_pem = """-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApViWEyoNK...... (your full public key) ...-----END PUBLIC KEY-----"""Option B: Environment Variables
Section titled “Option B: Environment Variables”export OCTOFHIR__AUTH__SIGNING__PRIVATE_KEY_PEM="$(cat jwt_private.pem)"export OCTOFHIR__AUTH__SIGNING__PUBLIC_KEY_PEM="$(cat jwt_public.pem)"export OCTOFHIR__AUTH__SIGNING__KID="production-key-2024"
# Start the servercargo runOption C: Separate Configuration File
Section titled “Option C: Separate Configuration File”Create a separate jwt-keys.toml:
[auth.signing]private_key_pem = """-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----"""
public_key_pem = """-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----"""kid = "production-key-2024"Then merge it with your main config using the config manager.
Key Management Best Practices
Section titled “Key Management Best Practices”Security
Section titled “Security”-
Never commit private keys to version control
- Add
*.pemto.gitignore - Use environment variables or secure secret management systems
- Add
-
Protect private key files
Terminal window chmod 600 jwt_private.pem -
Use secure storage
- For production, consider using HashiCorp Vault, AWS Secrets Manager, or similar
- Mount secrets as environment variables or files at runtime
Key Rotation
Section titled “Key Rotation”The configuration includes key_rotation_days and keys_to_keep settings for future automatic key rotation support:
[auth.signing]algorithm = "RS384"key_rotation_days = 90 # Rotate keys every 90 dayskeys_to_keep = 3 # Keep 3 old keys for validationNote: Automatic rotation is planned for a future release. Currently, you must rotate keys manually.
Key ID (kid) Management
Section titled “Key ID (kid) Management”The kid (Key ID) field helps clients identify which key was used to sign a token. Best practices:
- Use descriptive, stable IDs:
production-key-2024-01instead of random UUIDs - Include rotation date: Makes it easy to track key age
- Keep it stable: Don’t change the kid unless you rotate the actual key
Troubleshooting
Section titled “Troubleshooting”Tokens Still Invalid After Restart
Section titled “Tokens Still Invalid After Restart”Problem: Tokens are invalidated even with keys configured.
Solutions:
- Verify both
private_key_pemandpublic_key_pemare set - Check that the
kidis stable (set explicitly) - Ensure the algorithm matches (
RS256,RS384, orES384) - Check server logs for key loading errors
”Failed to load JWT signing key from configuration”
Section titled “”Failed to load JWT signing key from configuration””Problem: Server logs show an error loading keys.
Solutions:
- Verify PEM format includes BEGIN/END markers
- Ensure no extra whitespace or formatting issues
- Check that private/public keys are a matching pair
- Verify the algorithm matches the key type (RSA for RS256/RS384, EC for ES384)
Validation Errors
Section titled “Validation Errors”Run validation:
# Check if keys are validopenssl rsa -in jwt_private.pem -checkopenssl ec -in jwt_private.pem -check # For EC keys
# Verify public key matches private keyopenssl rsa -in jwt_private.pem -pubout | diff - jwt_public.pemDocker Deployment
Section titled “Docker Deployment”For Docker deployments, use secrets or environment variables:
# DockerfileFROM rust:latest AS builderWORKDIR /appCOPY . .RUN cargo build --release
FROM debian:bookworm-slimCOPY --from=builder /app/target/release/octofhir-server /usr/local/bin/# Don't copy keys to the image!CMD ["octofhir-server"]services: octofhir: image: octofhir-server environment: OCTOFHIR__AUTH__SIGNING__PRIVATE_KEY_PEM: ${JWT_PRIVATE_KEY} OCTOFHIR__AUTH__SIGNING__PUBLIC_KEY_PEM: ${JWT_PUBLIC_KEY} OCTOFHIR__AUTH__SIGNING__KID: "production-2024" secrets: - jwt_private_key - jwt_public_key
secrets: jwt_private_key: file: ./secrets/jwt_private.pem jwt_public_key: file: ./secrets/jwt_public.pemKubernetes Deployment
Section titled “Kubernetes Deployment”Use Kubernetes secrets:
# Create secret from fileskubectl create secret generic jwt-keys \ --from-file=private=./jwt_private.pem \ --from-file=public=./jwt_public.pem
# DeploymentapiVersion: v1kind: Podmetadata: name: octofhirspec: containers: - name: octofhir image: octofhir-server env: - name: OCTOFHIR__AUTH__SIGNING__PRIVATE_KEY_PEM valueFrom: secretKeyRef: name: jwt-keys key: private - name: OCTOFHIR__AUTH__SIGNING__PUBLIC_KEY_PEM valueFrom: secretKeyRef: name: jwt-keys key: public - name: OCTOFHIR__AUTH__SIGNING__KID value: "production-2024"Migration from Auto-Generated Keys
Section titled “Migration from Auto-Generated Keys”If you’re migrating from auto-generated keys:
- Generate and configure new keys following the steps above
- Deploy the new configuration to your server
- Users will need to re-authenticate once (their old tokens will be invalid)
- Future restarts will maintain token validity
To minimize disruption, deploy during a maintenance window.
Verification
Section titled “Verification”After configuration, the server logs should show:
INFO octofhir_server::server: Loaded JWT signing key from configuration algorithm: RS384 kid: production-key-2024Instead of:
WARN octofhir_server::server: Generated new JWT signing key - tokens will be invalidated on server restart. Consider setting auth.signing.private_key_pem in configuration for production.Related Documentation
Section titled “Related Documentation”- Authentication Guide - OAuth 2.0 and SMART on FHIR
- Auth Architecture - Authentication system architecture
- Deployment Guide - Production deployment best practices