Passing MetadataAutomatic HeadersPython (FastAPI / Flask)

Python (FastAPI / Flask)

Use dependency injection patterns in FastAPI and request context in Flask for automatic user context.

FastAPI with Dependency Injection

Basic Setup

# requirements.txt
fastapi
uvicorn
python-jose[cryptography]
python-multipart
tokenlay

JWT Dependency

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from tokenlay import Tokenlay
import os
 
app = FastAPI()
security = HTTPBearer(auto_error=False)
tokenlay = Tokenlay(api_key=os.getenv("TOKENLAY_API_KEY"))
 
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
ALGORITHM = "HS256"
 
async def get_user_context(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
    """Extract user context from JWT token."""
    
    if not credentials:
        return {
            "User-ID": "anonymous",
            "User-Role": "guest",
            "Auth-Status": "no-token"
        }
    
    try:
        payload = jwt.decode(
            credentials.credentials, 
            SECRET_KEY, 
            algorithms=[ALGORITHM]
        )
        
        return {
            "User-ID": payload.get("sub"),
            "User-Email": payload.get("email"),
            "User-Role": payload.get("role", "user"),
            "Organization-ID": payload.get("org"),
            "Auth-Status": "authenticated",
            "Token-Type": payload.get("token_type", "access")
        }
        
    except JWTError as e:
        return {
            "User-ID": "anonymous",
            "User-Role": "guest",
            "Auth-Status": "invalid-token",
            "Auth-Error": str(e)
        }
 
@app.post("/api/process")
async def process_data(
    data: dict, 
    user_context: dict = Depends(get_user_context)
):
    """Process data with automatic user context injection."""
    
    try:
        result = await tokenlay.process(data, headers=user_context)
        return result
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=str(e)
        )

Advanced FastAPI Patterns

Custom User Model

from pydantic import BaseModel
from typing import Optional, List
from fastapi import Depends
from datetime import datetime
 
class User(BaseModel):
    id: str
    email: str
    role: str
    organization_id: Optional[str] = None
    permissions: List[str] = []
    is_active: bool = True
 
class UserContext(BaseModel):
    user_id: str
    user_email: str
    user_role: str
    organization_id: Optional[str] = None
    permissions: str = ""
    auth_status: str
    request_id: str
 
async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> Optional[User]:
    """Get current authenticated user."""
    
    if not credentials:
        return None
    
    try:
        payload = jwt.decode(
            credentials.credentials,
            SECRET_KEY,
            algorithms=[ALGORITHM]
        )
        
        user_id = payload.get("sub")
        if not user_id:
            return None
        
        # Fetch user from database (replace with your implementation)
        user = await get_user_by_id(user_id)
        return user
        
    except JWTError:
        return None
 
async def create_user_context(
    user: Optional[User] = Depends(get_current_user)
) -> UserContext:
    """Create user context headers from user data."""
    
    if not user:
        return UserContext(
            user_id="anonymous",
            user_email="",
            user_role="guest",
            auth_status="unauthenticated",
            request_id=generate_request_id()
        )
    
    return UserContext(
        user_id=user.id,
        user_email=user.email,
        user_role=user.role,
        organization_id=user.organization_id,
        permissions=",".join(user.permissions),
        auth_status="authenticated",
        request_id=generate_request_id()
    )
 
@app.post("/api/process")
async def process_with_typed_context(
    data: dict,
    context: UserContext = Depends(create_user_context)
):
    """Process data with typed user context."""
    
    headers = context.dict(exclude_none=True, by_alias=True)
    
    # Convert to header format
    header_dict = {
        f"User-{key.replace('_', '-').title()}": str(value)
        for key, value in headers.items()
        if value is not None
    }
    
    result = await tokenlay.process(data, headers=header_dict)
    return result
 
def generate_request_id() -> str:
    """Generate unique request ID."""
    import uuid
    return str(uuid.uuid4())
 
async def get_user_by_id(user_id: str) -> Optional[User]:
    """Fetch user from database (implement based on your setup)."""
    # Replace with actual database query
    pass

Permission-Based Dependencies

from functools import wraps
from fastapi import HTTPException, status
 
def require_permission(permission: str):
    """Decorator to require specific permission."""
    
    async def permission_dependency(
        user: Optional[User] = Depends(get_current_user)
    ):
        if not user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Authentication required"
            )
        
        if permission not in user.permissions:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Permission '{permission}' required"
            )
        
        return user
    
    return permission_dependency
 
def require_role(role: str):
    """Decorator to require specific role."""
    
    async def role_dependency(
        user: Optional[User] = Depends(get_current_user)
    ):
        if not user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Authentication required"
            )
        
        if user.role != role:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Role '{role}' required"
            )
        
        return user
    
    return role_dependency
 
# Usage
@app.post("/api/admin/process")
async def admin_process(
    data: dict,
    user: User = Depends(require_role("admin")),
    context: UserContext = Depends(create_user_context)
):
    """Admin-only processing endpoint."""
    
    headers = {
        "User-ID": user.id,
        "User-Role": user.role,
        "Admin-Action": "true",
        "Request-ID": context.request_id
    }
    
    result = await tokenlay.process(data, headers=headers)
    return result

Flask Implementation

Basic Flask Setup

from flask import Flask, request, g, jsonify
from functools import wraps
import jwt
import os
from tokenlay import Tokenlay
 
app = Flask(__name__)
tokenlay = Tokenlay(api_key=os.getenv("TOKENLAY_API_KEY"))
 
def extract_user_context(f):
    """Decorator to extract user context from JWT."""
    
    @wraps(f)
    def decorated_function(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        
        if auth_header and auth_header.startswith('Bearer '):
            token = auth_header[7:]  # Remove 'Bearer ' prefix
            
            try:
                payload = jwt.decode(
                    token, 
                    os.getenv('JWT_SECRET'), 
                    algorithms=["HS256"]
                )
                
                g.user_context = {
                    "User-ID": payload.get("sub"),
                    "User-Email": payload.get("email"),
                    "User-Role": payload.get("role", "user"),
                    "Organization-ID": payload.get("org"),
                    "Auth-Status": "authenticated"
                }
                
                g.user = payload
                
            except jwt.InvalidTokenError as e:
                g.user_context = {
                    "User-ID": "anonymous",
                    "User-Role": "guest",
                    "Auth-Status": "invalid-token",
                    "Auth-Error": str(e)
                }
                g.user = None
        else:
            g.user_context = {
                "User-ID": "anonymous",
                "User-Role": "guest",
                "Auth-Status": "no-token"
            }
            g.user = None
            
        return f(*args, **kwargs)
    
    return decorated_function
 
@app.route('/api/process', methods=['POST'])
@extract_user_context
def process_data():
    """Process data with automatic user context."""
    
    try:
        data = request.get_json()
        result = tokenlay.process(data, headers=g.user_context)
        return jsonify(result)
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

Advanced Flask Patterns

Blueprint with User Context

from flask import Blueprint, g, request, jsonify
from functools import wraps
 
api_bp = Blueprint('api', __name__, url_prefix='/api')
 
class UserContextManager:
    """Centralized user context management."""
    
    @staticmethod
    def extract_from_request():
        """Extract user context from current request."""
        
        auth_header = request.headers.get('Authorization')
        user_context = {
            "Request-ID": generate_request_id(),
            "Request-Timestamp": datetime.utcnow().isoformat(),
            "Client-IP": request.remote_addr,
            "User-Agent": request.headers.get('User-Agent', '')[:100]
        }
        
        if auth_header and auth_header.startswith('Bearer '):
            token = auth_header[7:]
            
            try:
                payload = jwt.decode(
                    token,
                    os.getenv('JWT_SECRET'),
                    algorithms=["HS256"]
                )
                
                user_context.update({
                    "User-ID": payload.get("sub"),
                    "User-Email": payload.get("email"),
                    "User-Role": payload.get("role", "user"),
                    "Organization-ID": payload.get("org"),
                    "Auth-Status": "authenticated",
                    "Token-Expires": str(payload.get("exp", ""))
                })
                
                return user_context, payload
                
            except jwt.ExpiredSignatureError:
                user_context.update({
                    "User-ID": "anonymous",
                    "Auth-Status": "token-expired"
                })
                
            except jwt.InvalidTokenError as e:
                user_context.update({
                    "User-ID": "anonymous",
                    "Auth-Status": "invalid-token",
                    "Auth-Error": str(e)
                })
        else:
            user_context.update({
                "User-ID": "anonymous",
                "Auth-Status": "no-token"
            })
        
        return user_context, None
 
def with_user_context(f):
    """Decorator that adds user context to flask.g."""
    
    @wraps(f)
    def decorated_function(*args, **kwargs):
        g.user_context, g.user = UserContextManager.extract_from_request()
        return f(*args, **kwargs)
    
    return decorated_function
 
@api_bp.route('/process', methods=['POST'])
@with_user_context
def process_endpoint():
    """Process data with user context."""
    
    data = request.get_json()
    
    try:
        result = tokenlay.process(data, headers=g.user_context)
        return jsonify(result)
        
    except Exception as e:
        app.logger.error(f"Processing failed: {e}", extra={
            "user_id": g.user_context.get("User-ID"),
            "request_id": g.user_context.get("Request-ID")
        })
        return jsonify({"error": "Processing failed"}), 500
 
# Register blueprint
app.register_blueprint(api_bp)

Role and Permission Checking

def require_auth(f):
    """Require authenticated user."""
    
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not g.user or g.user_context.get("Auth-Status") != "authenticated":
            return jsonify({"error": "Authentication required"}), 401
        return f(*args, **kwargs)
    
    return decorated_function
 
def require_role(required_role):
    """Require specific user role."""
    
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            user_role = g.user_context.get("User-Role")
            if user_role != required_role:
                return jsonify({
                    "error": f"Role '{required_role}' required"
                }), 403
            return f(*args, **kwargs)
        
        return decorated_function
    return decorator
 
def require_permission(permission):
    """Require specific permission."""
    
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            user_permissions = g.user.get("permissions", []) if g.user else []
            if permission not in user_permissions:
                return jsonify({
                    "error": f"Permission '{permission}' required"
                }), 403
            return f(*args, **kwargs)
        
        return decorated_function
    return decorator
 
# Usage examples
@api_bp.route('/admin/process', methods=['POST'])
@with_user_context
@require_auth
@require_role('admin')
def admin_process():
    """Admin-only processing."""
    
    data = request.get_json()
    
    # Add admin-specific headers
    admin_context = {
        **g.user_context,
        "Admin-Action": "true",
        "Admin-User-ID": g.user_context.get("User-ID")
    }
    
    result = tokenlay.process(data, headers=admin_context)
    return jsonify(result)
 
@api_bp.route('/premium/process', methods=['POST'])
@with_user_context
@require_auth
@require_permission('premium_features')
def premium_process():
    """Premium feature processing."""
    
    data = request.get_json()
    
    premium_context = {
        **g.user_context,
        "Feature-Tier": "premium",
        "Priority": "high"
    }
    
    result = tokenlay.process(data, headers=premium_context)
    return jsonify(result)

Error Handling and Logging

Comprehensive Error Handling

# FastAPI
from fastapi import Request
from fastapi.responses import JSONResponse
import logging
 
logger = logging.getLogger(__name__)
 
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    """Global exception handler with user context logging."""
    
    user_context = getattr(request.state, 'user_context', {})
    
    logger.error(
        f"Unhandled exception: {exc}",
        extra={
            "user_id": user_context.get("User-ID"),
            "request_path": str(request.url),
            "request_method": request.method
        }
    )
    
    return JSONResponse(
        status_code=500,
        content={"error": "Internal server error"}
    )
 
# Flask
@app.errorhandler(Exception)
def handle_exception(e):
    """Global exception handler for Flask."""
    
    user_context = getattr(g, 'user_context', {})
    
    app.logger.error(
        f"Unhandled exception: {e}",
        extra={
            "user_id": user_context.get("User-ID"),
            "request_path": request.path,
            "request_method": request.method,
            "request_id": user_context.get("Request-ID")
        }
    )
    
    return jsonify({"error": "Internal server error"}), 500

Request Logging

# FastAPI middleware
from fastapi import Request, Response
import time
import uuid
 
@app.middleware("http")
async def log_requests(request: Request, call_next):
    """Log all requests with user context."""
    
    start_time = time.time()
    request_id = str(uuid.uuid4())
    
    # Add request ID to state
    request.state.request_id = request_id
    
    response = await call_next(request)
    
    process_time = time.time() - start_time
    
    user_context = getattr(request.state, 'user_context', {})
    
    logger.info(
        "Request completed",
        extra={
            "request_id": request_id,
            "method": request.method,
            "path": str(request.url.path),
            "status_code": response.status_code,
            "process_time": process_time,
            "user_id": user_context.get("User-ID", "anonymous")
        }
    )
    
    return response
 
# Flask before/after request
@app.before_request
def before_request():
    """Log request start."""
    g.start_time = time.time()
    g.request_id = str(uuid.uuid4())
 
@app.after_request
def after_request(response):
    """Log request completion."""
    
    if hasattr(g, 'start_time'):
        process_time = time.time() - g.start_time
        user_context = getattr(g, 'user_context', {})
        
        app.logger.info(
            "Request completed",
            extra={
                "request_id": getattr(g, 'request_id', 'unknown'),
                "method": request.method,
                "path": request.path,
                "status_code": response.status_code,
                "process_time": process_time,
                "user_id": user_context.get("User-ID", "anonymous")
            }
        )
    
    return response

Testing

FastAPI Testing

from fastapi.testclient import TestClient
import pytest
from jose import jwt
from datetime import datetime, timedelta
 
client = TestClient(app)
 
def create_test_token(user_data: dict) -> str:
    """Create JWT token for testing."""
    
    expire = datetime.utcnow() + timedelta(hours=1)
    token_data = {
        **user_data,
        "exp": expire
    }
    
    return jwt.encode(token_data, SECRET_KEY, algorithm=ALGORITHM)
 
def test_authenticated_request():
    """Test request with valid authentication."""
    
    token = create_test_token({
        "sub": "test-user-123",
        "email": "test@example.com",
        "role": "admin"
    })
    
    response = client.post(
        "/api/process",
        json={"data": "test"},
        headers={"Authorization": f"Bearer {token}"}
    )
    
    assert response.status_code == 200
 
def test_unauthenticated_request():
    """Test request without authentication."""
    
    response = client.post(
        "/api/process",
        json={"data": "test"}
    )
    
    assert response.status_code == 200  # Should still work with anonymous context
 
def test_invalid_token():
    """Test request with invalid token."""
    
    response = client.post(
        "/api/process",
        json={"data": "test"},
        headers={"Authorization": "Bearer invalid-token"}
    )
    
    assert response.status_code == 200  # Should work with anonymous context

Flask Testing

import pytest
from your_app import app
 
@pytest.fixture
def client():
    app.config['TESTING'] = True
    with app.test_client() as client:
        yield client
 
def test_user_context_extraction(client):
    """Test user context extraction."""
    
    token = create_test_token({
        "sub": "test-user-456",
        "email": "test@example.com",
        "role": "user"
    })
    
    response = client.post(
        '/api/process',
        json={"data": "test"},
        headers={"Authorization": f"Bearer {token}"}
    )
    
    assert response.status_code == 200
 
def test_role_based_access(client):
    """Test role-based access control."""
    
    user_token = create_test_token({
        "sub": "regular-user",
        "role": "user"
    })
    
    admin_token = create_test_token({
        "sub": "admin-user",
        "role": "admin"
    })
    
    # Regular user should be rejected
    response = client.post(
        '/api/admin/process',
        json={"data": "test"},
        headers={"Authorization": f"Bearer {user_token}"}
    )
    assert response.status_code == 403
    
    # Admin should be allowed
    response = client.post(
        '/api/admin/process',
        json={"data": "test"},
        headers={"Authorization": f"Bearer {admin_token}"}
    )
    assert response.status_code == 200

Best Practices

Security

  • Always validate JWT signatures and expiration
  • Use environment variables for secrets
  • Implement proper CORS policies
  • Log security-related events
  • Use HTTPS in production

Performance

  • Cache user data when appropriate
  • Use async/await for I/O operations
  • Implement connection pooling for databases
  • Consider token validation performance
  • Use dependency injection for reusable components

Monitoring

  • Log all authentication events
  • Monitor token usage patterns
  • Track API performance metrics
  • Set up alerts for authentication failures
  • Use structured logging for better analysis