gateway/docs/code-documentation/security-api.md

509 lines
13 KiB
Markdown

# Security API Documentation
API documentation for security-related endpoints and how to use security components in routes.
## Table of Contents
1. [Overview](#overview)
2. [Authentication Endpoints](#authentication-endpoints)
3. [Authentication Flows](#authentication-flows)
4. [Using Security in Routes](#using-security-in-routes)
5. [API Examples](#api-examples)
## Overview
The Security component provides authentication endpoints and utilities that routes can use to protect their endpoints and access user context. This document focuses on the API surface and how to use security components from route handlers.
For detailed information about the Security component architecture and internal workings, see [Security Component Documentation](./security-component.md).
## Authentication Endpoints
### Local Authentication
**Base Path**: `/api/local`
#### POST `/api/local/login`
Authenticate with username and password.
**Request**:
- Content-Type: `application/x-www-form-urlencoded`
- Headers: `X-CSRF-Token` (required)
- Body: `username`, `password` (form data)
**Response**:
- Status: `200 OK`
- Cookies: `auth_token` (httpOnly), `refresh_token` (httpOnly)
- Body:
```json
{
"type": "local_auth_success",
"message": "Login successful - tokens set in httpOnly cookies",
"authenticationAuthority": "local",
"expires_at": "2024-01-01T12:00:00"
}
```
**Rate Limit**: 30 requests per minute
#### POST `/api/local/register`
Register a new user account.
**Request**:
- Content-Type: `application/json`
- Body: User object + password
**Response**: User object
**Rate Limit**: 10 requests per minute
#### GET `/api/local/me`
Get current authenticated user information.
**Request**:
- Headers: `Authorization: Bearer <token>` OR Cookie: `auth_token`
**Response**: User object
**Rate Limit**: 30 requests per minute
#### POST `/api/local/refresh`
Refresh access token using refresh token.
**Request**:
- Cookie: `refresh_token` (httpOnly)
**Response**:
- Cookies: New `auth_token` and `refresh_token`
- Body: Token refresh response
**Rate Limit**: 60 requests per minute
#### POST `/api/local/logout`
Logout current user and invalidate tokens.
**Request**:
- Headers: `Authorization: Bearer <token>` OR Cookie: `auth_token`
**Response**: Logout confirmation
**Rate Limit**: 10 requests per minute
### Microsoft OAuth Authentication
**Base Path**: `/api/msft`
#### GET `/api/msft/login`
Initiate Microsoft OAuth login flow.
**Response**: Redirects to Microsoft OAuth login page
#### GET `/api/msft/auth/callback`
OAuth callback endpoint (handled by Microsoft).
**Query Parameters**:
- `code`: Authorization code from Microsoft
- `state`: State parameter for CSRF protection
**Response**: HTML page that sets cookies and redirects
#### GET `/api/msft/me`
Get current authenticated user information.
**Request**:
- Headers: `Authorization: Bearer <token>` OR Cookie: `auth_token`
**Response**: User object
#### POST `/api/msft/logout`
Logout current user.
**Request**:
- Headers: `Authorization: Bearer <token>` OR Cookie: `auth_token`
**Response**: Logout confirmation
### Google OAuth Authentication
**Base Path**: `/api/google`
#### GET `/api/google/login`
Initiate Google OAuth login flow.
**Query Parameters**:
- `state`: Optional state parameter (default: "login")
- `connectionId`: Optional connection ID for connection flow
**Response**: Redirects to Google OAuth login page
#### GET `/api/google/auth/callback`
OAuth callback endpoint (handled by Google).
**Query Parameters**:
- `code`: Authorization code from Google
- `state`: State parameter for CSRF protection
**Response**: HTML page that sets cookies and redirects
#### GET `/api/google/me`
Get current authenticated user information.
**Request**:
- Headers: `Authorization: Bearer <token>` OR Cookie: `auth_token`
**Response**: User object
#### POST `/api/google/logout`
Logout current user.
**Request**:
- Headers: `Authorization: Bearer <token>` OR Cookie: `auth_token`
**Response**: Logout confirmation
## Authentication Flows
### Login Flow (Local Authentication)
```mermaid
sequenceDiagram
participant Client
participant Route as Login Route<br/>POST /api/local/login
participant Auth as auth.py
participant JWT as jwtService.py
participant Interface as Interface Layer
participant DB as Database
Client->>Route: POST /api/local/login<br/>(username, password, X-CSRF-Token)
Route->>Route: Validate CSRF Token
Route->>Interface: authenticateLocalUser(username, password)
Interface->>DB: Query User & Verify Password
DB-->>Interface: User Record
alt Invalid Credentials
Interface-->>Route: None
Route-->>Client: HTTPException 401
end
Route->>JWT: createAccessToken(userData)
JWT->>JWT: Generate JTI (UUID)
JWT->>JWT: Set Expiration
JWT->>JWT: Sign JWT
JWT-->>Route: Access Token + Expires At
Route->>JWT: createRefreshToken(userData)
JWT-->>Route: Refresh Token + Expires At
Route->>Interface: Save Token to DB<br/>(for LOCAL authority)
Interface->>DB: Insert Token Record
Route->>JWT: setAccessTokenCookie(response, token)
JWT->>JWT: Set httpOnly Cookie<br/>(secure, samesite=strict)
Route->>JWT: setRefreshTokenCookie(response, token)
JWT->>JWT: Set httpOnly Cookie
Route-->>Client: HTTP Response<br/>(with Cookies)
```
### OAuth Flow (Microsoft/Google)
```mermaid
sequenceDiagram
participant Client
participant Route as OAuth Route<br/>GET /api/{provider}/login
participant Provider as OAuth Provider<br/>(MSFT/Google)
participant JWT as jwtService.py
participant Interface as Interface Layer
participant DB as Database
Client->>Route: GET /api/{provider}/login
Route->>Route: Generate OAuth State
Route->>Provider: Redirect to OAuth URL<br/>(with state, client_id, redirect_uri)
Provider-->>Client: OAuth Login Page
Client->>Provider: User Authenticates
Provider->>Route: GET /api/{provider}/callback<br/>(code, state)
Route->>Route: Validate State
Route->>Provider: Exchange Code for Tokens<br/>(POST /token)
Provider-->>Route: Access Token + Refresh Token
Route->>Provider: Get User Info<br/>(using Access Token)
Provider-->>Route: User Profile Data
Route->>Interface: Find/Create User<br/>(by external ID)
Interface->>DB: Query/Create User
DB-->>Interface: User Record
Interface-->>Route: User Object
Route->>Interface: Save/Create Connection
Interface->>DB: Save Connection Record
Route->>Interface: Save Token
Interface->>DB: Save Token Record
Route->>JWT: createAccessToken(userData)
JWT-->>Route: Gateway JWT Token
Route->>JWT: setAccessTokenCookie(response, token)
Route->>JWT: setRefreshTokenCookie(response, token)
Route-->>Client: HTTP Response<br/>(with Gateway Cookies)
```
## Using Security in Routes
### Basic Authentication Pattern
All protected routes use the `getCurrentUser` dependency to access the authenticated user:
```python
from fastapi import APIRouter, Depends
from modules.security.auth import getCurrentUser
from modules.datamodels.datamodelUam import User
router = APIRouter(prefix="/api/example", tags=["Example"])
@router.get("/protected")
async def protected_endpoint(
currentUser: User = Depends(getCurrentUser)
) -> dict:
"""
Protected endpoint that requires authentication.
The currentUser parameter is automatically populated by getCurrentUser.
"""
return {
"message": f"Hello, {currentUser.username}!",
"userId": currentUser.id,
"mandateId": currentUser.mandateId
}
```
### Rate Limiting
Use the `limiter` from the security module to add rate limiting:
```python
from modules.security.auth import getCurrentUser, limiter
from fastapi import Request
@router.post("/action")
@limiter.limit("30/minute")
async def rate_limited_endpoint(
request: Request,
currentUser: User = Depends(getCurrentUser)
) -> dict:
"""Endpoint with rate limiting (30 requests per minute)"""
return {"status": "success"}
```
### Cookie vs Header Authentication
The security component supports both authentication methods:
1. **Cookie-based** (preferred for web apps):
- Tokens are automatically sent via httpOnly cookies
- More secure (not accessible via JavaScript)
- Automatically included in requests from same origin
2. **Header-based** (for API clients):
- Use `Authorization: Bearer <token>` header
- Required for programmatic API access
- Tokens can be obtained from login endpoints
The `CookieAuth` class checks cookies first, then falls back to headers.
### Creating Tokens in Routes
When implementing custom authentication endpoints, use the JWT service:
```python
from modules.security.jwtService import (
createAccessToken,
createRefreshToken,
setAccessTokenCookie,
setRefreshTokenCookie
)
from modules.datamodels.datamodelUam import AuthAuthority
@router.post("/custom-login")
async def custom_login(
response: Response,
username: str,
password: str
):
# Verify credentials...
user = verify_user(username, password)
# Create token data
token_data = {
"sub": user.username,
"mandateId": str(user.mandateId),
"userId": str(user.id),
"authenticationAuthority": AuthAuthority.LOCAL
}
# Create tokens
access_token, expires_at = createAccessToken(token_data)
refresh_token, _ = createRefreshToken(token_data)
# Set cookies
setAccessTokenCookie(response, access_token)
setRefreshTokenCookie(response, refresh_token)
return {"status": "success", "expires_at": expires_at.isoformat()}
```
### Clearing Tokens (Logout)
Use the JWT service cookie clearing functions:
```python
from modules.security.jwtService import (
clearAccessTokenCookie,
clearRefreshTokenCookie
)
@router.post("/logout")
async def logout(
response: Response,
currentUser: User = Depends(getCurrentUser)
):
# Clear cookies
clearAccessTokenCookie(response)
clearRefreshTokenCookie(response)
# Optionally revoke token in database
# interface.revokeToken(tokenId)
return {"status": "logged_out"}
```
## API Examples
### Example: Protected Endpoint
```python
from fastapi import APIRouter, Depends, HTTPException
from modules.security.auth import getCurrentUser, limiter
from modules.datamodels.datamodelUam import User
from fastapi import Request
router = APIRouter(prefix="/api/data", tags=["Data"])
@router.get("/items")
@limiter.limit("60/minute")
async def get_items(
request: Request,
currentUser: User = Depends(getCurrentUser)
) -> list:
"""
Get items for the current user.
Requires authentication.
"""
# User is guaranteed to be authenticated and enabled
# Access user properties: currentUser.id, currentUser.mandateId, etc.
items = fetch_user_items(currentUser.id)
return items
```
### Example: Role-Based Access
```python
from modules.datamodels.datamodelUam import UserPrivilege
@router.delete("/admin/items/{item_id}")
async def delete_item(
item_id: str,
currentUser: User = Depends(getCurrentUser)
):
"""
Delete item (admin only).
"""
# Check user privilege
if currentUser.privilege not in [UserPrivilege.ADMIN, UserPrivilege.SYSADMIN]:
raise HTTPException(
status_code=403,
detail="Admin access required"
)
delete_item_by_id(item_id)
return {"status": "deleted"}
```
### Example: Mandate-Scoped Access
```python
@router.get("/mandate/data")
async def get_mandate_data(
currentUser: User = Depends(getCurrentUser)
):
"""
Get data scoped to user's mandate.
"""
# User's mandateId is automatically validated during authentication
# Token context must match user's current mandate
data = fetch_mandate_data(currentUser.mandateId)
return data
```
### Example: CSRF Protection
For state-changing operations, ensure CSRF token is included:
```python
@router.post("/update")
async def update_data(
request: Request,
data: dict,
currentUser: User = Depends(getCurrentUser)
):
"""
Update data (requires CSRF token).
CSRF middleware automatically validates X-CSRF-Token header.
"""
# CSRF validation happens automatically via middleware
# Just ensure client sends X-CSRF-Token header
update_user_data(currentUser.id, data)
return {"status": "updated"}
```
### Example: Error Handling
```python
from fastapi import HTTPException, status
@router.get("/sensitive")
async def get_sensitive_data(
currentUser: User = Depends(getCurrentUser)
):
"""
Get sensitive data with proper error handling.
"""
try:
# getCurrentUser already validates:
# - Token is valid and not expired
# - User exists and is enabled
# - Context matches (mandateId, userId)
data = fetch_sensitive_data(currentUser.id)
return data
except ValueError as e:
# Handle business logic errors
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
```
## Related Documentation
- [Security Component Documentation](./security-component.md) - Component architecture and internal workings
- [Architecture Overview](./architecture-overview.md) - Overall system architecture