Files
fotospiel-app/docs/deployment/oauth-key-rotation.md
Codex Agent 6290a3a448 Fix tenant event form package selector so it no longer renders empty-value options, handles loading/empty
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)
2025-10-19 23:00:47 +02:00

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_STORE points to a shared filesystem (default storage/app/oauth-keys).
  • OAUTH_JWT_KID set to the current signing key id.
  • Application deploy tooling able to propagate .env changes promptly.
  • Operations access to run artisan commands in the target environment.

Rotation Workflow

  1. Review existing keys

    php artisan oauth:list-keys
    

    Confirm the current entry matches OAUTH_JWT_KID, note any legacy KIDs that should remain trusted until rotation completes.

  2. 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 /archive folder but leaves it in-place for token verification.
    • After the command, run php artisan oauth:list-keys again to verify both the old and new KIDs exist.
  3. Update environment configuration

    • Set OAUTH_JWT_KID to the newly generated value.
    • Deploy the updated config (restart queue workers/web instances if they cache config).
  4. Smoke test issuance

    • Request a fresh OAuth token (PKCE flow) and inspect the JWT header — kid must 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.
  5. Monitor

    • Watch application logs for Invalid token / JWT public key not found errors over the next 24h.
    • Investigate any anomalies before pruning.

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-run first to see which directories would be removed.
  • The prune command never deletes the current KID.
  • 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.