Identity-Backend - Service secrets, scale to KID
Scaling JWT Secrets: From Single Key to Key Rotation with kid
When you first start building microservices, a single shared secret for JWT signing and verification feels simple and sufficient. But as soon as you introduce multiple clients, microservices, and the need for key rotation, this model starts breaking down.
We just ran into this exact scenario with our identity-backend and logging-backend services.
Current Setup
-
Identity-Backend: Issues JWT tokens signed with
JWT_SECRET_KEY. -
Logging-Backend: Validates JWT tokens using the same secret.
-
Audience (
aud) and Issuer (iss): Hardcoded to enforce proper service-to-service calls. -
Single Secret in
.env: Shared between both services.
Pros:
-
Very simple to implement.
-
Easy to debug in early MVP stages.
Cons:
-
Any secret rotation requires redeploying all services simultaneously.
-
Impossible to issue different secrets for different clients.
-
No audit trail for which key signed what.
Next Evolution: Key IDs (kid) and Client Secrets
We’re introducing key IDs (kid) in JWT headers and moving to a model where identity-backend manages multiple keys in a SQLite database:
-
Each client has:
-
client_id(e.g.,careergpt-backend) -
key_id(e.g.,key-2025-01) -
secret(random string)
-
-
JWTs are issued with:
-
kidin the header (so consumers know which key to verify against).
-
-
logging-backendextractskidand retrieves the right secret to validate the token.
This enables:
-
Per-Client Secrets: Different clients get unique keys.
-
Key Rotation: Add a new key, start signing with it, keep verifying old tokens until expiry.
-
Audit & Traceability: Easily identify which key signed which token.
How Key Rotation Works
-
Add new key to
identity-backendDB (e.g.,key-2025-02). -
Start issuing tokens with new key.
-
Keep old key in DB for verification until tokens expire.
-
Remove old key when no longer needed.
Implementation Plan
Step 1: Identity-Backend
-
Add
client_secretstable. -
Endpoints:
-
POST /add-client-secret→ Add new key for a client. -
GET /list-client-secrets→ Admin view of active keys.
-
Step 2: Logging-Backend
-
Extract
kidfrom JWT header. -
Request correct secret from Identity-Backend for verification.
Step 3: Transition
-
Keep current single-secret fallback (
JWT_SECRET_KEY) until rollout is complete. -
Gradually migrate clients to new key-ID model.
Why This Matters
This pattern future-proofs your architecture for:
-
Microservice sprawl (multiple services, each with unique secrets).
-
Zero-downtime rotations (no redeploys to rotate secrets).
-
Enhanced security posture (different secrets = limited blast radius).
Next Post
In the next post, we’ll share the code changes:
-
How to embed
kidin JWT headers using PyJWT. -
Database schema and migrations.
-
Verification logic changes in
logging-backend.
Comments
Post a Comment