JWT Configuration
JWT Configuration
Section titled “JWT Configuration”Configure JSON Web Tokens (JWT) for API authentication in Mantis.
Overview
Section titled “Overview”JWT Authentication Flow
Section titled “JWT Authentication Flow”JWT Structure
Section titled “JWT Structure”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLWlkIiwidGVuYW50IjoiYWJjMTIzIiwiZXhwIjoxNzA0MDY3MjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c| Part | Description |
|---|---|
| Header | Algorithm and token type |
| Payload | Claims (user, tenant, expiry) |
| Signature | Cryptographic signature |
Algorithm Selection
Section titled “Algorithm Selection”Supported Algorithms
Section titled “Supported Algorithms”| Algorithm | Type | Key | Use Case |
|---|---|---|---|
| HS256 | Symmetric | Shared secret | Simple deployments |
| RS256 | Asymmetric | RSA key pair | Enterprise, key separation |
HS256 (HMAC-SHA256)
Section titled “HS256 (HMAC-SHA256)”Uses a shared secret key:
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ HS256 Key Flow ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤â ââ Same secret key used for: ââ ⢠Signing tokens (Mandible) ââ ⢠Verifying tokens (Mandible) ââ ââ Pros: Simple, fast ââ Cons: Secret must be shared with all verifiers ââ ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââRS256 (RSA-SHA256)
Section titled “RS256 (RSA-SHA256)”Uses public/private key pair:
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ RS256 Key Flow ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤â ââ Private key: Signs tokens (keep secret) ââ Public key: Verifies tokens (can be shared) ââ ââ Pros: Key separation, public verification ââ Cons: Slower, larger tokens ââ ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââConfiguration
Section titled “Configuration”HS256 Configuration
Section titled “HS256 Configuration”[jwt]algorithm = "HS256"secret = "your-256-bit-secret-key-here-at-least-32-chars"access_token_expiry_secs = 3600 # 1 hourrefresh_token_expiry_secs = 2592000 # 30 daysissuer = "mandible"Generate a secure secret:
# Generate 256-bit secretopenssl rand -base64 32
# Or using /dev/urandomhead -c 32 /dev/urandom | base64RS256 Configuration
Section titled “RS256 Configuration”[jwt]algorithm = "RS256"private_key_path = "/etc/mantis/jwt/private.pem"public_key_path = "/etc/mantis/jwt/public.pem"access_token_expiry_secs = 3600 # 1 hourrefresh_token_expiry_secs = 2592000 # 30 daysissuer = "mandible"Generate RSA key pair:
# Generate private keyopenssl genrsa -out /etc/mantis/jwt/private.pem 2048
# Extract public keyopenssl rsa -in /etc/mantis/jwt/private.pem \ -pubout -out /etc/mantis/jwt/public.pem
# Secure private keychmod 600 /etc/mantis/jwt/private.pemEnvironment Variables
Section titled “Environment Variables”| Variable | Description | Example |
|---|---|---|
MANDIBLE__JWT__ALGORITHM | Algorithm | HS256 or RS256 |
MANDIBLE__JWT__SECRET | HS256 secret | your-secret-key |
MANDIBLE__JWT__PRIVATE_KEY_PATH | RS256 private key | /etc/mantis/jwt/private.pem |
MANDIBLE__JWT__PUBLIC_KEY_PATH | RS256 public key | /etc/mantis/jwt/public.pem |
MANDIBLE__JWT__ACCESS_TOKEN_EXPIRY_SECS | Token lifetime (seconds) | 3600 |
MANDIBLE__JWT__REFRESH_TOKEN_EXPIRY_SECS | Refresh lifetime (secs) | 604800 |
MANDIBLE__JWT__ISSUER | Token issuer | mandible |
MANDIBLE__JWT__AUDIENCE | Token audience | lens |
MANDIBLE__JWT__KEY_ID | Current signing key ID | k1 |
Token Lifetimes
Section titled “Token Lifetimes”Access Token
Section titled “Access Token”Short-lived token for API access:
| Setting | Default | Recommended Range |
|---|---|---|
access_token_expiry_secs | 3600 | 900-86400 seconds |
Refresh Token
Section titled “Refresh Token”Longer-lived token to obtain new access tokens:
| Setting | Default | Recommended Range |
|---|---|---|
refresh_token_expiry_secs | 2592000 (30 days) | 86400-2592000 seconds |
Token Refresh Flow
Section titled “Token Refresh Flow”JWT Claims
Section titled “JWT Claims”Standard Claims
Section titled “Standard Claims”| Claim | Description | Example |
|---|---|---|
sub | Subject (user ID) | usr_abc123 |
iss | Issuer | mandible |
exp | Expiration time | 1704067200 |
iat | Issued at | 1703980800 |
jti | Token ID | tok_xyz789 |
Custom Claims
Section titled “Custom Claims”| Claim | Description | Example |
|---|---|---|
tenant_id | Tenant identifier | ten_abc123 |
roles | User roles (array) | ["operator"] |
pending_2fa | Awaiting 2FA | true |
session_id | Session identifier | sess_abc123 |
Example payload:
{ "sub": "usr_abc123", "iss": "mandible", "aud": "lens", "exp": 1704067200, "iat": 1703980800, "nbf": 1703980800, "jti": "tok_xyz789", "email": "user@example.com", "username": "jsmith", "tenant_id": "ten_abc123", "roles": ["operator"], "session_id": "sess_abc123"}API Key Authentication
Section titled “API Key Authentication”API Keys vs JWT
Section titled “API Keys vs JWT”| Aspect | JWT | API Key |
|---|---|---|
| Lifetime | Short (hours) | Long (months/years) |
| Revocation | On expiry | Manual |
| Use case | User sessions | Service accounts |
| Contains claims | Yes | No (server lookup) |
API Key Storage
Section titled “API Key Storage”API keys are always Argon2-hashed before storage and identified by the mnt_ prefix.
There is no [api_keys] configuration section â hashing is built in and not configurable.
Creating API Keys
Section titled “Creating API Keys”API keys are created through the REST API (or the Lens UI), not a CLI subcommand:
curl -X POST "https://api.mantis.example.com/api/v1/auth/api-keys" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "ci-pipeline", "description": "CI/CD deployment key"}'
# Response includes the full key exactly once in the "full_key" field:# {"full_key": "mnt_7K4vBz9mQxNwR5pLy2HjC8dTfG6sAeWuJn3iX1kM4o", "api_key": { ... }, "warning": "..."}# Store it securely - it cannot be retrieved later.Using API Keys
Section titled “Using API Keys”API keys use the Authorization: Bearer header with mnt_ prefix:
# API key authenticationcurl -H "Authorization: Bearer mnt_7K4vBz9mQxNwR5pLy2HjC8dTfG6sAeWuJn3iX1kM4o" \ https://mantis.example.com/api/v1/targetsSecurity Best Practices
Section titled “Security Best Practices”Secret Management
Section titled “Secret Management”HS256 secrets:
# Generate strong secret (at least 256 bits)openssl rand -base64 32
# Store in environment variable (not in config file)export MANDIBLE__JWT__SECRET="$(cat /etc/mantis/secrets/jwt-secret)"RS256 keys:
# Secure key file permissionschmod 600 /etc/mantis/jwt/private.pemchmod 644 /etc/mantis/jwt/public.pemchown mantis:mantis /etc/mantis/jwt/*.pemToken Security
Section titled “Token Security”| Practice | Implementation |
|---|---|
| Short expiry | 1-24 hours for access tokens |
| Secure transmission | HTTPS only |
| HttpOnly cookies | Prevent XSS access |
| SameSite cookies | Prevent CSRF |
Cookie Configuration
Section titled “Cookie Configuration”[cookie]secure = true # HTTPS onlysame_site = "Lax" # CSRF protectiondomain = ".example.com" # Cookie domainpath = "/" # Cookie pathToken Validation
Section titled “Token Validation”Validation Steps
Section titled “Validation Steps”Mandible performs these checks:
- Signature verification - Token not tampered
- Expiration check - Token not expired
- Issuer check - Correct issuer claim
- Token ID check - Not revoked (if tracking enabled)
Token Revocation
Section titled “Token Revocation”For immediate token invalidation, revoke the underlying sessions through the REST API.
Logging out (POST /api/v1/auth/logout) blocklists the current access token in Redis
and revokes all of the user’s OAuth2 refresh tokens, so no new access tokens can be minted
from the refresh cookie. It does not directly revoke UserSession records — use
DELETE /api/v1/auth/sessions to revoke those:
# Revoke a specific session by IDcurl -X DELETE "https://api.mantis.example.com/api/v1/auth/sessions/$SESSION_ID" \ -H "Authorization: Bearer $TOKEN"
# Revoke all of the current user's OTHER sessions (the current session is kept active)curl -X DELETE "https://api.mantis.example.com/api/v1/auth/sessions" \ -H "Authorization: Bearer $TOKEN"Redis availability dependency (fail-closed)
Section titled “Redis availability dependency (fail-closed)”The JWT revocation (blocklist) check is backed by Redis, and it fail-closes: if the blocklist Redis backend is unreachable, Mandible treats the token as revoked and returns 401. This is a deliberate security choice â a Redis outage must not let a revoked token through â but it has an important availability consequence:
Mandible exports these Prometheus metrics for the dependency:
| Metric | Meaning |
|---|---|
mantis_jwt_blocklist_redis_available | 1 if the JWT blocklist Redis is reachable, 0 if not (set on the request path). |
mantis_jwt_blocklist_redis_errors_total | Cumulative blocklist Redis errors, labelled by operation. |
mantis_sse_redis_up | Background-probed (~10s) SSE/session Redis reachability, independent of traffic. |
The bundled alert rules (deploy/ansible/roles/monitoring/files/mandible-alerts.yaml)
page on mantis_jwt_blocklist_redis_available == 0 (critical) and warn on SSE
Redis being down. When this fires, follow the
Redis availability runbook:
restore Redis, or make a conscious decision (e.g. reduce JWT TTL) while it is
recovered.
Key Rotation
Section titled “Key Rotation”HS256 Key Rotation
Section titled “HS256 Key Rotation”Support multiple keys during rotation:
[jwt]algorithm = "HS256"# Current key (used for signing)secret = "new-secret-key"# Previous keys (used for verification only)previous_secret = "old-secret-key"key_id = "k2"previous_key_id = "k1"Rotation process:
- Add new secret as primary, move old secret to
previous_secret - Assign a new
key_idand setprevious_key_idto the old key ID - Wait for old tokens to expire
- Remove
previous_secretandprevious_key_id
RS256 Key Rotation
Section titled “RS256 Key Rotation”[jwt]algorithm = "RS256"private_key_path = "/etc/mantis/jwt/private-new.pem"public_key_path = "/etc/mantis/jwt/public-new.pem"key_id = "k2"previous_public_key_path = "/etc/mantis/jwt/public-old.pem"previous_key_id = "k1"See Key Rotation for detailed procedures.
OIDC Integration
Section titled “OIDC Integration”The [jwt] table configures only the signing keys for Mantis’s own tokens. Accepting
tokens from external identity providers (OIDC/JWKS) is not configured under [jwt] â
there are no [jwt.external], [jwt.validation], jwks_uri, or jwks_refresh_interval
keys. External identity providers are configured at runtime through the admin
identity-providers API and stored in the database, where each provider carries its own
issuer/audience/JWKS settings.
See Identity Providers and OIDC Configuration for the full setup.
Troubleshooting
Section titled “Troubleshooting”Invalid Token Errors
Section titled “Invalid Token Errors”| Error | Cause | Solution |
|---|---|---|
invalid signature | Wrong secret/key | Verify key configuration |
token expired | Past expiration | Request new token |
invalid issuer | Wrong issuer claim | Check issuer configuration |
invalid audience | Wrong audience claim | Check audience configuration |
Debugging Tokens
Section titled “Debugging Tokens”Decode JWT (without verification):
# Decode headerecho "$JWT" | cut -d. -f1 | base64 -d 2>/dev/null | jq
# Decode payloadecho "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jqOnline tools (development only):
Common Issues
Section titled “Common Issues”Token immediately expires:
# Check server time synchronizationtimedatectl status
# Sync time if neededtimedatectl set-ntp trueSignature verification fails:
# Verify secret is correctecho -n "test" | openssl dgst -sha256 -hmac "$SECRET"
# For RS256, verify key pair matchesopenssl rsa -in private.pem -pubout | diff - public.pemToken not accepted after key rotation:
# Ensure previous keys are configuredgrep previous_secret /etc/mantis/mandible.toml
# Check token was signed with tracked keyProduction Configuration
Section titled “Production Configuration”Recommended Settings
Section titled “Recommended Settings”[jwt]algorithm = "RS256" # Prefer asymmetricprivate_key_path = "/etc/mantis/jwt/private.pem"public_key_path = "/etc/mantis/jwt/public.pem"access_token_expiry_secs = 28800 # 8 hoursrefresh_token_expiry_secs = 604800 # 7 daysissuer = "mandible"
[cookie]secure = truesame_site = "Lax"High Security Settings
Section titled “High Security Settings”Tighten security with short token lifetimes, RS256, and an explicit audience. These are the
only JWT knobs the server exposes â there is no require_token_binding field or
[jwt.validation] section:
[jwt]algorithm = "RS256"private_key_path = "/etc/mantis/jwt/private.pem"public_key_path = "/etc/mantis/jwt/public.pem"access_token_expiry_secs = 3600 # 1 hourrefresh_token_expiry_secs = 86400 # Daily re-authissuer = "mandible"audience = "lens"Next Steps
Section titled “Next Steps”- OIDC Configuration - External identity providers
- Key Rotation - Rotating JWT keys
- Security Overview - Overall security architecture
