-
Notifications
You must be signed in to change notification settings - Fork 346
Description
Is your feature request related to a problem? Please describe.
After upgrading to Supabase CLI v2.71.1+, our local development environment started returning 401 Unauthorized on all authenticated requests.
The root cause is that v2.71.1 changed the default JWT signing algorithm from HS256 (symmetric) to ES256 (asymmetric). Our backend validates JWTs using a (legacy) JWT secret, with HS256 (https://supabase.com/docs/guides/auth/signing-keys).
After the upgrade, all tokens are ES256, causing validation to fail -> "The specified alg value is not allowed."
This change was released as a patch version labeled fix: use asymmetric signing key by default, with no migration documentation or opt-out mechanism.
It's technically a breaking change and not everyone on the team uses the same supabase cli version.
Describe the solution you'd like
Add a config.toml option to explicitly choose the JWT signing algorithm:
[auth]
# Choose JWT signing algorithm: "ES256" (default) or "HS256" (legacy)
jwt_algorithm = "HS256"This would allow us to control the algorithm of our local Supabase, so we can migrate to ES256 on our own timeline.
Describe alternatives you've considered
- Downgrading the CLI - Works but blocks access to new features and bug fixes
- Updating backend code to support both algorithms - We implemented this (added JWKS client for ES256 verification), but it required significant debugging time to identify the issue and non-trivial code changes
- Better documentation - At minimum, documenting this as a breaking change in the v2.71.1 release notes with migration instructions would have helped
Additional context
For anyone else hitting this issue, here's the workaround we implemented to support both HS256 and ES256 tokens, until the entire team migrates past 2.71.1.
import jwt
from jwt import PyJWKClient
def validate_token(token: str):
header = jwt.get_unverified_header(token)
if header.get("alg") == "ES256":
jwks_client = PyJWKClient(f"{SUPABASE_URL}/auth/v1/.well-known/jwks.json")
signing_key = jwks_client.get_signing_key_from_jwt(token)
return jwt.decode(token, signing_key.key, algorithms=["ES256"], audience="authenticated")
else:
return jwt.decode(token, JWT_SECRET, algorithms=["HS256"], audience="authenticated")