modcomms/backend/app/dependencies/auth.py
michael 321a9ca820 Implement Microsoft MSAL SSO with PKCE flow
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>
2025-12-16 08:43:30 -06:00

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