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:
@@ -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.
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user