Frontend: - Add @azure/msal-browser and @azure/msal-react packages - Create authConfig.ts with MSAL configuration for PKCE flow - Create authService.ts for token acquisition and user info - Wrap App with MsalProvider in index.tsx - Replace dummy login with real MSAL loginPopup() in Login.tsx - Update App.tsx to use useIsAuthenticated/useMsal hooks - Update Profile.tsx to display real user data from claims - Update geminiService.ts to include access_token in WebSocket messages - Update WIPReviewer.tsx to pass msalInstance for auth Backend: - Add python-jose and httpx dependencies for JWT verification - Create auth_service.py with Azure AD JWKS fetching and token verification - Create auth.py FastAPI dependency for protected REST endpoints - Update main.py to verify tokens on WebSocket and protect /info endpoint - Add AZURE_TENANT_ID, AZURE_CLIENT_ID, DISABLE_AUTH to config 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
"""
|
|
FastAPI authentication dependencies.
|
|
|
|
Provides dependency functions for securing REST endpoints with Azure AD token verification.
|
|
"""
|
|
from typing import Optional
|
|
from fastapi import Header, HTTPException, status
|
|
|
|
from app.services.auth_service import verify_access_token
|
|
|
|
|
|
async def get_current_user(authorization: Optional[str] = Header(None)) -> dict:
|
|
"""
|
|
FastAPI dependency to verify the access token and return user claims.
|
|
|
|
Use as a dependency on protected endpoints:
|
|
@app.get("/protected")
|
|
async def protected_route(user: dict = Depends(get_current_user)):
|
|
return {"message": f"Hello {user.get('name')}"}
|
|
|
|
Args:
|
|
authorization: The Authorization header value (Bearer <token>)
|
|
|
|
Returns:
|
|
The token claims dict containing user information
|
|
|
|
Raises:
|
|
HTTPException: 401 if token is missing or invalid
|
|
"""
|
|
if not authorization:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Missing authorization header",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
# Extract token from "Bearer <token>" format
|
|
parts = authorization.split()
|
|
if len(parts) != 2 or parts[0].lower() != "bearer":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authorization header format. Expected: Bearer <token>",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
token = parts[1]
|
|
claims = await verify_access_token(token)
|
|
|
|
if not claims:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid or expired token",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
return claims
|