fix: accept ES256 (ECC P-256) JWT keys alongside RS256 in backend auth

Supabase JWT key was switched to ECC P-256, but the JWKS verification
only accepted RS256. Add ES256 to the accepted algorithms list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 11:52:42 +01:00
parent 80d5d01993
commit e935bc4d32
3 changed files with 62 additions and 3 deletions

View File

@@ -0,0 +1,10 @@
---
# nuzlocke-tracker-snft
title: Support ES256 (ECC P-256) JWT keys in backend auth
status: in-progress
type: bug
created_at: 2026-03-22T10:51:30Z
updated_at: 2026-03-22T10:51:30Z
---
Backend JWKS verification only accepts RS256 algorithm, but Supabase JWT key was switched to ECC P-256 (ES256). This causes 401 errors on all authenticated requests. Fix: accept both RS256 and ES256 in the algorithms list, and update tests accordingly.

View File

@@ -60,7 +60,7 @@ def _verify_jwt_hs256(token: str) -> dict | None:
def _verify_jwt(token: str) -> dict | None: def _verify_jwt(token: str) -> dict | None:
"""Verify JWT using JWKS (RS256), falling back to HS256 shared secret.""" """Verify JWT using JWKS (RS256/ES256), falling back to HS256 shared secret."""
client = _get_jwks_client() client = _get_jwks_client()
if client: if client:
try: try:
@@ -68,7 +68,7 @@ def _verify_jwt(token: str) -> dict | None:
return jwt.decode( return jwt.decode(
token, token,
signing_key.key, signing_key.key,
algorithms=["RS256"], algorithms=["RS256", "ES256"],
audience="authenticated", audience="authenticated",
) )
except jwt.InvalidTokenError: except jwt.InvalidTokenError:

View File

@@ -4,7 +4,7 @@ from uuid import UUID
import jwt import jwt
import pytest import pytest
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import ec, rsa
from httpx import ASGITransport, AsyncClient from httpx import ASGITransport, AsyncClient
from app.core.auth import AuthUser, get_current_user, require_admin, require_auth from app.core.auth import AuthUser, get_current_user, require_admin, require_auth
@@ -73,6 +73,55 @@ def mock_jwks_client(rsa_key_pair):
return mock_client return mock_client
@pytest.fixture(scope="module")
def ec_key_pair():
"""Generate EC P-256 key pair for testing."""
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
return private_key, public_key
@pytest.fixture
def valid_es256_token(ec_key_pair):
"""Generate a valid ES256 JWT token."""
private_key, _ = ec_key_pair
payload = {
"sub": "user-456",
"email": "ec-user@example.com",
"role": "authenticated",
"aud": "authenticated",
"exp": int(time.time()) + 3600,
}
return jwt.encode(payload, private_key, algorithm="ES256")
@pytest.fixture
def mock_jwks_client_ec(ec_key_pair):
"""Create a mock JWKS client that returns our test EC public key."""
_, public_key = ec_key_pair
mock_client = MagicMock()
mock_signing_key = MagicMock()
mock_signing_key.key = public_key
mock_client.get_signing_key_from_jwt.return_value = mock_signing_key
return mock_client
async def test_get_current_user_valid_es256_token(
valid_es256_token, mock_jwks_client_ec
):
"""Test get_current_user works with ES256 (ECC P-256) tokens."""
with patch("app.core.auth._get_jwks_client", return_value=mock_jwks_client_ec):
class MockRequest:
headers = {"Authorization": f"Bearer {valid_es256_token}"}
user = get_current_user(MockRequest())
assert user is not None
assert user.id == "user-456"
assert user.email == "ec-user@example.com"
assert user.role == "authenticated"
async def test_get_current_user_valid_token(valid_token, mock_jwks_client): async def test_get_current_user_valid_token(valid_token, mock_jwks_client):
"""Test get_current_user returns user for valid token.""" """Test get_current_user returns user for valid token."""
with patch("app.core.auth._get_jwks_client", return_value=mock_jwks_client): with patch("app.core.auth._get_jwks_client", return_value=mock_jwks_client):