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

13 KiB

Security API Documentation

API documentation for security-related endpoints and how to use security components in routes.

Table of Contents

  1. Overview
  2. Authentication Endpoints
  3. Authentication Flows
  4. Using Security in Routes
  5. 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.

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:
{
  "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)

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)

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:

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:

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"}

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:

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:

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

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

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

@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:

@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

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)
        )