--- # nuzlocke-tracker-t9aj title: Migrate JWT verification from HS256 shared secret to asymmetric keys (JWKS) status: completed type: task priority: low created_at: 2026-03-21T11:14:29Z updated_at: 2026-03-21T13:01:33Z --- The backend currently verifies Supabase JWTs using an HS256 shared secret (`SUPABASE_JWT_SECRET`). Supabase recommends migrating to asymmetric keys (RS256) for better security.\n\nInstead of storing a shared secret, the backend would fetch public keys from Supabase's JWKS endpoint (`https://.supabase.co/.well-known/jwks.json`) and verify tokens against those.\n\n## Changes needed\n\n- [x] Update `backend/src/app/core/auth.py` to fetch and cache JWKS public keys\n- [x] Change `jwt.decode` from `HS256` to `RS256` with the fetched public key\n- [x] Remove `SUPABASE_JWT_SECRET` from config, docker-compose, deploy workflow, and .env files\n- [x] Update tests\n\n## References\n\n- https://supabase.com/docs/guides/auth/signing-keys\n- https://supabase.com/docs/guides/auth/jwts ## Summary of Changes - Added `cryptography==45.0.3` dependency for RS256 support - Updated `auth.py` to use `PyJWKClient` for fetching and caching JWKS public keys from `{SUPABASE_URL}/.well-known/jwks.json` - Changed JWT verification from HS256 to RS256 - Removed `supabase_jwt_secret` from config.py - Updated docker-compose.yml: removed `SUPABASE_JWT_SECRET`, backend now uses JWKS from GoTrue URL - Updated docker-compose.prod.yml: replaced `SUPABASE_JWT_SECRET` with `SUPABASE_URL` - Updated deploy.yml: deploy workflow now writes `SUPABASE_URL` instead of `SUPABASE_JWT_SECRET` - Updated .env.example files: removed `SUPABASE_JWT_SECRET` references - Rewrote tests to use RS256 tokens with mocked JWKS client **Note:** For production, add `SUPABASE_URL` to your GitHub secrets (should point to your Supabase project URL like `https://your-project.supabase.co`).