This guide helps you migrate from global JWT algorithm configuration to per-tenant configuration.
Uitsmijter supports per-tenant JWT algorithm selection, allowing different tenants to use HS256 or RS256 independently. This enables:
- Zero-downtime migration: Migrate tenants from HS256 to RS256 one at a time
- Mixed deployments: Run HS256 and RS256 tenants in the same instance
- Gradual rollout: Test RS256 with pilot tenants before full deployment
Compatibility: Per-tenant algorithm selection is available in Uitsmijter CE 0.11.0 and later.
Current behavior:
- All tenants use the same algorithm (from
JWT_ALGORITHMenvironment variable) - Default: HS256
New behavior:
- Each tenant can specify
jwt_algorithmin its configuration - Tenants without
jwt_algorithmfall back toJWT_ALGORITHM(backward compatible) - Default fallback: HS256
Existing configurations continue to work without changes:
Before (all tenants use global setting):
# Environment
JWT_ALGORITHM: HS256
# Tenant (no algorithm specified)
apiVersion: "uitsmijter.io/v1"
kind: Tenant
metadata:
name: my-tenant
spec:
hosts:
- example.com
After (same behavior):
# Environment
JWT_ALGORITHM: HS256 # Still used by tenants without jwt_algorithm
# Tenant (no algorithm specified → inherits HS256 from environment)
apiVersion: "uitsmijter.io/v1"
kind: Tenant
metadata:
name: my-tenant
spec:
hosts:
- example.com
No action required for existing deployments.
Goal: Move all tenants from HS256 to RS256 at once (simple deployments)
Steps:
- Update resource servers to support JWKS (see JWT Algorithms - Migration)
- Set global
JWT_ALGORITHM=RS256 - Restart Uitsmijter
- Wait for old HS256 tokens to expire (2× token lifetime)
No tenant configuration changes needed (tenants inherit global setting).
Goal: Migrate tenants one at a time (recommended for multi-tenant production)
Steps:
- Update resource servers to support both HS256 and RS256:
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: 'https://id.example.com/.well-known/jwks.json',
cache: true
});
function getKey(header, callback) {
if (header.alg === 'HS256') {
callback(null, process.env.JWT_SECRET);
} else {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
}
jwt.verify(token, getKey, { algorithms: ['HS256', 'RS256'] }, (err, decoded) => {
// Handles both HS256 and RS256 tokens
});
-
Choose a pilot tenant for initial RS256 rollout:
apiVersion: "uitsmijter.io/v1" kind: Tenant metadata: name: pilot-tenant spec: jwt_algorithm: RS256 # ← Tenant-specific override hosts: - pilot.example.com -
Apply the tenant update:
kubectl apply -f pilot-tenant.yaml # or for file-based: # Restart Uitsmijter to reload tenant config -
Monitor pilot tenant:
- Verify new tokens have
alg: RS256in header - Check resource servers correctly verify tokens
- Monitor for 2× token lifetime (4 hours default)
- Verify new tokens have
-
Migrate remaining tenants (one at a time or in batches):
spec: jwt_algorithm: RS256 # ← Add to each tenant -
Update global default (optional, after all tenants migrated):
JWT_ALGORITHM: RS256 # New global default -
Clean up tenant configs (optional): Remove
jwt_algorithmfrom tenants (will inherit global RS256).
Goal: Keep some tenants on HS256, migrate others to RS256 (permanent mixed state)
Use cases:
- Legacy tenants require HS256 for compatibility
- Internal tools use HS256, customer-facing apps use RS256
- Different security tiers
Configuration:
# Global fallback
JWT_ALGORITHM: HS256 # Default for tenants without jwt_algorithm
---
# Tenant A: Internal tools (uses global HS256)
apiVersion: "uitsmijter.io/v1"
kind: Tenant
metadata:
name: internal-tools
spec:
hosts:
- internal.example.com
# No jwt_algorithm → inherits HS256
---
# Tenant B: Customer portal (overrides with RS256)
apiVersion: "uitsmijter.io/v1"
kind: Tenant
metadata:
name: customer-portal
spec:
jwt_algorithm: RS256 # Tenant-specific override
hosts:
- portal.example.com
---
# Tenant C: Legacy app (explicitly stays on HS256)
apiVersion: "uitsmijter.io/v1"
kind: Tenant
metadata:
name: legacy-app
spec:
jwt_algorithm: HS256 # Explicit HS256 (not relying on global default)
hosts:
- legacy.example.com
Kubernetes:
kubectl get tenant my-tenant -o yaml | grep jwt_algorithm
Expected output (tenant-specific):
jwt_algorithm: RS256
Expected output (inheriting global):
# (no jwt_algorithm field)
- Obtain a token for the tenant
- Decode the JWT header:
echo "TOKEN_HERE" | cut -d'.' -f1 | base64 -d | jq - Check
algfield:{ "alg": "RS256", "typ": "JWT", "kid": "2024-11-10" }
curl https://id.example.com/.well-known/jwks.json | jq
Expected output (if any tenant uses RS256):
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "2024-11-10",
"n": "...",
"e": "AQAB"
}
]
}
Expected output (all tenants use HS256):
{
"keys": []
}
If issues occur, you can rollback individual tenants:
Rollback single tenant to HS256:
spec:
jwt_algorithm: HS256 # Revert to HS256
Rollback all tenants:
Remove jwt_algorithm from all tenants, ensure JWT_ALGORITHM=HS256 (or unset).
Important: After rollback, RS256 tokens issued before rollback will fail verification.
Symptoms: 401 Unauthorized errors for specific tenant
Diagnosis:
- Check tenant algorithm:
kubectl get tenant TENANT_NAME -o yaml | grep jwt_algorithm - Check token header: Decode JWT, verify
algmatches tenant configuration - Check resource server: Ensure it supports the algorithm (HS256 or RS256)
Solution: Ensure resource server configuration matches tenant algorithm.
Symptoms: {"keys": []} even though tenant uses RS256
Diagnosis:
kubectl get tenants -o yaml | grep jwt_algorithm
Solution: Verify at least one tenant has jwt_algorithm: RS256. Restart Uitsmijter to regenerate keys.
Symptoms: Some tenants work, others fail with “invalid signature”
Diagnosis: Check resource server configuration
Solution: Ensure resource server supports both HS256 and RS256 (see example above).
- Test in staging first: Always test per-tenant algorithms in staging before production
- Monitor during migration: Watch for verification errors during rollout
- Migrate gradually: Start with 1-2 pilot tenants, then expand
- Document tenant algorithms: Keep a list of which tenants use which algorithm
- Plan token expiration: Wait 2× token lifetime after changes before cleanup
- Update resource servers first: Always update resource servers before changing tenant algorithms
- JWT Algorithms - Detailed algorithm comparison
- Tenant Configuration - Full tenant configuration reference
- JWKS Endpoint - JWKS endpoint documentation