508 lines
13 KiB
Markdown
508 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
|
|
|
|
|