Skip to content

Commit d3c8c8c

Browse files
committed
authentication
1 parent 2a615ab commit d3c8c8c

1 file changed

Lines changed: 97 additions & 9 deletions

File tree

app/core/auth/auth0_middleware.py

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1+
import json
2+
import logging
3+
from datetime import UTC, datetime
14
from typing import Optional
25
from uuid import uuid4
6+
37
import aiohttp
48
from fastapi import HTTPException, Request
5-
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
9+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
610
from jose import ExpiredSignatureError, jwt
7-
import json
8-
import logging
9-
from datetime import UTC, datetime
1011

1112
from app.core.config import get_settings
13+
from app.db.session import AsyncSessionLocal
14+
from app.models.domain.user import User
1215

1316
# from app.api.dependencies import get_db
1417
from app.repositories.implementations.user_repository import UserRepository
1518
from app.services.user_service import UserService
16-
from app.models.domain.user import User
17-
from app.db.session import AsyncSessionLocal
1819

1920
logger = logging.getLogger(__name__)
2021
settings = get_settings()
@@ -76,8 +77,9 @@ async def _get_jwks(self) -> dict:
7677
raise HTTPException(status_code=500, detail="Authentication service unavailable")
7778
return self.jwks
7879

80+
"""
7981
async def _verify_token(self, token: str) -> dict:
80-
"""Verify JWT token and return payload."""
82+
8183
try:
8284
unverified_header = jwt.get_unverified_header(token)
8385
logger.debug(f"Unverified token header: {json.dumps(unverified_header, indent=2)}")
@@ -94,14 +96,71 @@ async def _verify_token(self, token: str) -> dict:
9496
raise HTTPException(status_code=401, detail="Invalid token key")
9597
9698
payload = jwt.decode(token, rsa_key, algorithms=self.algorithms, audience=self.audience, issuer=self.issuer)
97-
logger.debug(f"Decoded token payload: {json.dumps(payload, indent=2)}")
99+
logger.debug("Decoded token claim names: %s", sorted(payload.keys()))
98100
return payload
99101
100102
except ExpiredSignatureError:
101103
raise HTTPException(status_code=401, detail="Token has expired")
102104
except Exception as e:
103105
logger.error(f"Token verification error: {str(e)}")
104106
raise HTTPException(status_code=401, detail="Invalid token")
107+
"""
108+
109+
async def _verify_token(self, token: str) -> dict:
110+
"""Verify JWT token locally and return its claims."""
111+
try:
112+
unverified_header = jwt.get_unverified_header(token)
113+
kid = unverified_header.get("kid")
114+
115+
if not kid:
116+
raise HTTPException(
117+
status_code=401,
118+
detail="Token is missing a key identifier",
119+
)
120+
121+
jwks = await self._get_jwks()
122+
rsa_key = next(
123+
(key for key in jwks.get("keys", []) if key.get("kid") == kid),
124+
None,
125+
)
126+
127+
if not rsa_key:
128+
raise HTTPException(
129+
status_code=401,
130+
detail="Invalid token key",
131+
)
132+
133+
payload = jwt.decode(
134+
token,
135+
rsa_key,
136+
algorithms=self.algorithms,
137+
audience=self.audience,
138+
issuer=self.issuer,
139+
)
140+
141+
# Log field names only, without personal values.
142+
logger.debug(
143+
"Decoded token claim names: %s",
144+
sorted(payload.keys()),
145+
)
146+
147+
return payload
148+
149+
except ExpiredSignatureError as exc:
150+
raise HTTPException(
151+
status_code=401,
152+
detail="Token has expired",
153+
) from exc
154+
155+
except HTTPException:
156+
raise
157+
158+
except Exception:
159+
logger.exception("Token verification failed")
160+
raise HTTPException(
161+
status_code=401,
162+
detail="Invalid token",
163+
)
105164

106165
async def _fetch_user_info(self, access_token: str) -> dict:
107166
"""Fetch additional user info from Auth0."""
@@ -127,8 +186,9 @@ def _generate_username(self, payload: dict) -> str:
127186
return email.split("@")[0]
128187
return f"user_{uuid4().hex[:8]}"
129188

189+
"""
130190
async def authenticate_request(self, request: Request) -> User:
131-
"""Authenticate a request and return the user."""
191+
132192
try:
133193
credentials = await self.security(request)
134194
if not credentials:
@@ -141,6 +201,34 @@ async def authenticate_request(self, request: Request) -> User:
141201
except Exception as e:
142202
logger.error(f"Authentication error: {str(e)}")
143203
raise HTTPException(status_code=401, detail="Authentication failed")
204+
"""
205+
206+
async def authenticate_request(self, request: Request) -> User:
207+
"""Authenticate a request and return the user."""
208+
try:
209+
credentials = await self.security(request)
210+
if not credentials:
211+
raise HTTPException(
212+
status_code=401,
213+
detail="No valid authentication credentials found",
214+
)
215+
216+
token = credentials.credentials
217+
218+
# Verify the token locally using the cached Auth0 JWKS.
219+
payload = await self._verify_token(token)
220+
221+
# Do not call Auth0 /userinfo for every API request.
222+
return await self._get_or_create_user(payload)
223+
224+
except HTTPException:
225+
raise
226+
except Exception:
227+
logger.exception("Unexpected authentication error")
228+
raise HTTPException(
229+
status_code=401,
230+
detail="Authentication failed",
231+
)
144232

145233
async def _get_or_create_user(self, user_data: dict) -> User:
146234
"""Get existing user or create new one."""

0 commit comments

Comments
 (0)