states, and pulls data from the authenticated /api/v1/tenant/packages endpoint.
(resources/js/admin/pages/EventFormPage.tsx, resources/js/admin/api.ts)
- Harden tenant-admin auth flow: prevent PKCE state loss, scope out StrictMode double-processing, add SPA
routes for /event-admin/login and /event-admin/logout, and tighten token/session clearing semantics (resources/js/admin/auth/{context,tokens}.tsx, resources/js/admin/pages/{AuthCallbackPage,LogoutPage}.tsx,
resources/js/admin/router.tsx, routes/web.php)
2.6 KiB
2.6 KiB
OAuth JWT Key Rotation Playbook (Dual-Key)
Purpose
Ensure marketing/tenant OAuth tokens remain valid during RSA key rotations by keeping the previous signing key available until all legacy tokens expire.
Prerequisites
- Environment variable
OAUTH_KEY_STOREpoints to a shared filesystem (defaultstorage/app/oauth-keys). OAUTH_JWT_KIDset to the current signing key id.- Application deploy tooling able to propagate
.envchanges promptly. - Operations access to run artisan commands in the target environment.
Rotation Workflow
-
Review existing keys
php artisan oauth:list-keysConfirm the
currententry matchesOAUTH_JWT_KID, note any legacy KIDs that should remain trusted until rotation completes. -
Generate new key pair
php artisan oauth:rotate-keys --kid=fotospiel-jwt-$(date +%Y%m%d%H%M)- The command now copies the existing key into the
/archivefolder but leaves it in-place for token verification. - After the command, run
php artisan oauth:list-keysagain to verify both the old and new KIDs exist.
- The command now copies the existing key into the
-
Update environment configuration
- Set
OAUTH_JWT_KIDto the newly generated value. - Deploy the updated config (restart queue workers/web instances if they cache config).
- Set
-
Smoke test issuance
- Request a fresh OAuth token (PKCE flow) and inspect the JWT header —
kidmust match the new value. - Use an existing token issued before the rotation to hit a tenant API route; it should continue to verify because the old key remains present.
- Request a fresh OAuth token (PKCE flow) and inspect the JWT header —
-
Monitor
- Watch application logs for
Invalid token/JWT public key not founderrors over the next 24h. - Investigate any anomalies before pruning.
- Watch application logs for
Pruning Legacy Keys
After the longest access-token + refresh-token lifetime (default: 30 days for refresh), prune the legacy signing directory.
php artisan oauth:prune-keys --days=45 --force
- Use
--dry-runfirst to see which directories would be removed. - The prune command never deletes the
currentKID. - Archived copies remain under
storage/app/oauth-keys/archive/...for forensics.
Runbook Summary
| Step | Command | Outcome |
|---|---|---|
| Inspect | php artisan oauth:list-keys |
Inventory current + legacy keys |
| Rotate | php artisan oauth:rotate-keys --kid=... |
Creates new key while keeping legacy key active |
| Verify | Issue new token + test old token | Ensures dual-key window works |
| Prune | php artisan oauth:prune-keys --days=45 |
Removes legacy key once safe |
Document completion of SEC-IO-01 in docs/todo/security-hardening-epic.md when the rotation runbook has been rehearsed in staging.