Files
orion/docs/architecture/auth-rbac.md

17 KiB

Authentication & Role-Based Access Control (RBAC)

Complete guide to the authentication and authorization system powering the multi-tenant platform.

Overview

The platform uses a JWT-based authentication system combined with role-based access control (RBAC) to secure all interfaces:

  • Admin interface
  • Vendor dashboard
  • Shop storefront
  • REST API endpoints

Authentication System

Technology Stack

  • JWT (JSON Web Tokens): Stateless authentication
  • bcrypt: Secure password hashing
  • Jose: JWT encoding/decoding library
  • FastAPI Security: OAuth2 password bearer flow

Authentication Flow

sequenceDiagram
    participant Client
    participant API
    participant AuthManager
    participant Database

    Client->>API: POST /api/v1/auth/login<br/>{username, password}
    API->>AuthManager: authenticate_user()
    AuthManager->>Database: Query user by username/email
    Database-->>AuthManager: User record
    AuthManager->>AuthManager: verify_password()
    AuthManager-->>API: User object
    API->>AuthManager: create_access_token()
    AuthManager-->>API: JWT token
    API-->>Client: {access_token, token_type, expires_in}

    Note over Client: Store token

    Client->>API: GET /api/v1/resource<br/>Authorization: Bearer <token>
    API->>AuthManager: verify_token()
    AuthManager->>AuthManager: Decode JWT
    AuthManager->>Database: Query user by ID
    Database-->>AuthManager: User object
    AuthManager-->>API: Current user
    API->>API: Process request
    API-->>Client: Resource data

User Roles

The platform has three distinct user roles, each with specific permissions and access levels:

Customer Role

Access: Public shop and own account space

Capabilities:

  • Browse vendor shops
  • Place orders
  • Manage their own account and order history
  • View order status
  • Update profile information
  • Can register directly from shop frontend

Account Creation: Self-registration via shop frontend (email verification required)

Authentication: Standard JWT authentication

Vendor Role

Access: Vendor area based on permissions

Types:

  • Vendor Owner: Full access to vendor dashboard and settings
  • Vendor Team Members: Access based on assigned permissions

Capabilities:

  • Manage products and inventory
  • Process orders
  • View analytics and reports
  • Configure shop settings (owners only)
  • Manage team members (owners only)
  • Access vendor-specific APIs

Account Creation:

  • Owners: Created automatically when admin creates a vendor
  • Team members: Invited by vendor owner via email

Permissions System: Team members can have granular permissions for different areas

Admin Role

Access: Full platform administration

Capabilities:

  • Manage all vendors
  • Create/manage vendor accounts
  • Access system settings
  • View all data across the platform
  • Manage users of all types
  • Access audit logs
  • Platform-wide analytics

Account Creation: Created by super admins on the backend

Super Privileges: Admins can access all areas including vendor and customer sections

Application Areas & Access Control

The platform has three distinct areas with different access requirements:

Area URL Pattern Access Purpose
Admin /admin/* or admin.platform.com Admin users only Platform administration and vendor management
Vendor /vendor/* Vendor owners and team members Vendor dashboard and shop management
Shop /shop/*, custom domains, subdomains Customers and public Public-facing eCommerce storefront
API /api/* All authenticated users (role-based) REST API for all operations

Account Registration Flow

Admin Accounts

  • Cannot register from frontend
  • Created by super admins on the backend
  • Used for: Platform administration

Vendor Accounts

  • Cannot register from frontend
  • Vendor Owners: Automatically created when admin creates a new vendor
  • Team Members: Invited by vendor owner via email invitation
  • Activation: Upon clicking email verification link

Customer Accounts

  • Can register directly on vendor shop
  • Activation: Upon clicking registration email link
  • Used for: Shopping and order management

Role Enforcement Methods

The AuthManager class provides several methods for role-based access control:

require_admin()

Restricts access to admin users only.

Usage:

from fastapi import Depends
from models.database.user import User
from middleware.auth import auth_manager

@app.get("/admin/dashboard")
async def admin_dashboard(
    current_user: User = Depends(auth_manager.require_admin)
):
    return {"message": "Admin access"}

Raises: AdminRequiredException if user is not admin

require_vendor()

Allows access to vendor users and admins.

Usage:

@app.get("/vendor/products")
async def vendor_products(
    current_user: User = Depends(auth_manager.require_vendor)
):
    return {"products": [...]}

Raises: InsufficientPermissionsException if user is not vendor or admin

require_customer()

Allows access to customer users and admins.

Usage:

@app.get("/shop/orders")
async def customer_orders(
    current_user: User = Depends(auth_manager.require_customer)
):
    return {"orders": [...]}

Raises: InsufficientPermissionsException if user is not customer or admin

require_role()

Custom role enforcement for specific roles. This method returns a decorator factory that creates role-checking decorators for any role name.

Method Signature:

def require_role(self, required_role: str) -> Callable

Parameters:

  • required_role (str): The exact role name required (e.g., "admin", "vendor", "custom_role")

Returns: A decorator function that:

  1. Accepts a function as input
  2. Returns a wrapper that validates the current user's role
  3. Raises HTTPException(403) if the role doesn't match

Usage Example:

from fastapi import Depends, APIRouter
from middleware.auth import auth_manager
from models.database.user import User

router = APIRouter()

@router.get("/moderator-only")
@auth_manager.require_role("moderator")
async def moderator_endpoint(current_user: User):
    """Only users with role='moderator' can access this."""
    return {"message": "Moderator access granted"}

# Can also be used with custom roles
@router.get("/special-access")
@auth_manager.require_role("special_user")
async def special_endpoint(current_user: User):
    return {"data": "special content"}

Error Response:

{
  "detail": "Required role 'moderator' not found. Current role: 'vendor'"
}

Note: For standard roles (admin, vendor, customer), prefer using the dedicated methods (require_admin(), require_vendor(), require_customer()) as they provide better error handling and custom exceptions.

create_default_admin_user()

Creates a default admin user if one doesn't already exist. This is typically used during initial application setup or database seeding.

Method Signature:

def create_default_admin_user(self, db: Session) -> User

Parameters:

  • db (Session): SQLAlchemy database session

Returns: User object (either the existing admin user or the newly created one)

Behavior:

  1. Checks if a user with username "admin" already exists
  2. If not found, creates a new admin user with:
    • Username: admin
    • Email: admin@example.com
    • Password: admin123 (hashed with bcrypt)
    • Role: admin
    • Status: Active
  3. If found, returns the existing user without modification

Usage Example:

from app.core.database import SessionLocal
from middleware.auth import auth_manager

# During application startup or database initialization
db = SessionLocal()
try:
    admin_user = auth_manager.create_default_admin_user(db)
    print(f"Admin user ready: {admin_user.username}")
finally:
    db.close()

Security Warning: ⚠️ The default credentials (admin / admin123) should be changed immediately after first login in production environments. Consider using environment variables for initial admin credentials:

# Example: Custom admin creation with env variables
import os

def create_admin_from_env(db: Session):
    admin_username = os.getenv("ADMIN_USERNAME", "admin")
    admin_password = os.getenv("ADMIN_PASSWORD", "admin123")
    admin_email = os.getenv("ADMIN_EMAIL", "admin@example.com")

    # Check if admin exists
    admin = db.query(User).filter(User.username == admin_username).first()
    if not admin:
        admin = User(
            username=admin_username,
            email=admin_email,
            hashed_password=auth_manager.hash_password(admin_password),
            role="admin",
            is_active=True
        )
        db.add(admin)
        db.commit()
    return admin

Typical Use Cases:

  • Initial database setup scripts
  • Application bootstrap/initialization
  • Development environment setup
  • Testing fixtures

JWT Token Structure

Token Payload

{
  "sub": "123",                          // User ID (JWT standard claim)
  "username": "testuser",                // Username for display
  "email": "user@example.com",           // User email
  "role": "vendor",                      // User role
  "exp": 1700000000,                     // Expiration timestamp (JWT standard)
  "iat": 1699999000                      // Issued at timestamp (JWT standard)
}

Token Configuration

Variable Description Default
JWT_SECRET_KEY Secret key for JWT signing Development key (change in production!)
JWT_EXPIRE_MINUTES Token expiration time in minutes 30

Environment Configuration:

# .env
JWT_SECRET_KEY=your-super-secret-key-change-in-production
JWT_EXPIRE_MINUTES=30

Permission Hierarchy

graph TD
    A[Admin] --> B[Full Platform Access]
    A --> C[Can Access All Areas]

    D[Vendor Owner] --> E[Vendor Dashboard]
    D --> F[Team Management]
    D --> G[Shop Settings]
    D --> H[All Vendor Data]

    I[Vendor Team Member] --> E
    I --> J[Limited Based on Permissions]

    K[Customer] --> L[Shop Access]
    K --> M[Own Orders]
    K --> N[Own Profile]

Admin Override: Admin users have implicit access to all areas, including vendor and customer sections. This allows admins to provide support and manage the platform effectively.

Security Features

Password Security

Hashing:

  • Algorithm: bcrypt
  • Automatic salt generation
  • Configurable work factor

Example:

from middleware.auth import auth_manager

# Hash password
hashed = auth_manager.hash_password("user_password")

# Verify password
is_valid = auth_manager.verify_password("user_password", hashed)

Token Security

Features:

  • Signed with secret key (prevents tampering)
  • Includes expiration time
  • Stateless (no server-side session storage)
  • Short-lived (30 minutes default)

Best Practices:

  • Use HTTPS in production
  • Store tokens securely on client
  • Implement token refresh mechanism
  • Clear tokens on logout

Protection Against Common Attacks

SQL Injection:

  • SQLAlchemy ORM with parameterized queries
  • Input validation with Pydantic

XSS (Cross-Site Scripting):

  • Jinja2 auto-escaping
  • Content Security Policy headers

CSRF (Cross-Site Request Forgery):

  • JWT tokens in Authorization header (not cookies)
  • SameSite cookie attribute for session cookies

Brute Force:

  • Rate limiting on auth endpoints
  • Account lockout after failed attempts (future)

Authentication Endpoints

Register User

POST /api/v1/auth/register
Content-Type: application/json

{
  "email": "user@example.com",
  "username": "testuser",
  "password": "securepassword123"
}

Response:

{
  "id": 123,
  "username": "testuser",
  "email": "user@example.com",
  "role": "customer",
  "is_active": true
}

Login

POST /api/v1/auth/login
Content-Type: application/json

{
  "username": "testuser",
  "password": "securepassword123"
}

Response:

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "token_type": "bearer",
  "expires_in": 1800
}

Using Authentication

Include the JWT token in the Authorization header:

GET /api/v1/resource
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

Error Handling

Authentication Errors

Error Status Code Description
InvalidCredentialsException 401 Username/password incorrect
InvalidTokenException 401 JWT token invalid or malformed
TokenExpiredException 401 JWT token has expired
UserNotActiveException 403 User account is inactive

Authorization Errors

Error Status Code Description
AdminRequiredException 403 Endpoint requires admin role
InsufficientPermissionsException 403 User lacks required permissions

Testing Authentication

Unit Tests

import pytest
from middleware.auth import AuthManager

def test_password_hashing():
    auth_manager = AuthManager()

    password = "test_password"
    hashed = auth_manager.hash_password(password)

    assert auth_manager.verify_password(password, hashed)
    assert not auth_manager.verify_password("wrong_password", hashed)

def test_create_token():
    auth_manager = AuthManager()
    user = create_test_user(role="vendor")

    token_data = auth_manager.create_access_token(user)

    assert "access_token" in token_data
    assert "token_type" in token_data
    assert token_data["token_type"] == "bearer"

Integration Tests

def test_login_flow(client):
    # Register user
    response = client.post("/api/v1/auth/register", json={
        "username": "testuser",
        "email": "test@example.com",
        "password": "password123"
    })
    assert response.status_code == 200

    # Login
    response = client.post("/api/v1/auth/login", json={
        "username": "testuser",
        "password": "password123"
    })
    assert response.status_code == 200
    token = response.json()["access_token"]

    # Access protected endpoint
    response = client.get(
        "/api/v1/profile",
        headers={"Authorization": f"Bearer {token}"}
    )
    assert response.status_code == 200

Best Practices

For Developers

  1. Always hash passwords: Never store plain text passwords
  2. Use dependency injection: Leverage FastAPI's Depends for auth
  3. Validate tokens: Always validate tokens on protected endpoints
  4. Check permissions: Verify user has required role/permissions
  5. Log auth events: Track login, logout, failed attempts

For Operations

  1. Strong secret keys: Use long, random JWT secret keys
  2. HTTPS only: Never send tokens over HTTP
  3. Token expiration: Keep token lifetimes short
  4. Rotate secrets: Periodically rotate JWT secret keys
  5. Monitor auth logs: Watch for suspicious activity

For Security

  1. Rate limiting: Limit auth endpoint requests
  2. Account lockout: Implement after N failed attempts
  3. Email verification: Require email confirmation
  4. Password policies: Enforce strong password requirements
  5. 2FA support: Consider adding two-factor authentication

Examples

Protecting an Endpoint

from fastapi import Depends, APIRouter
from sqlalchemy.orm import Session
from middleware.auth import auth_manager
from app.core.database import get_db
from models.database.user import User

router = APIRouter()

@router.get("/vendors")
async def get_vendors(
    current_user: User = Depends(auth_manager.require_admin),
    db: Session = Depends(get_db)
):
    """Only admins can list all vendors."""
    vendors = db.query(Vendor).all()
    return {"vendors": vendors}

Multi-Role Access

@router.get("/dashboard")
async def dashboard(
    current_user: User = Depends(auth_manager.get_current_user),
    db: Session = Depends(get_db)
):
    """Accessible by all authenticated users, but returns different data."""
    if current_user.role == "admin":
        # Admin sees everything
        data = get_admin_dashboard(db)
    elif current_user.role == "vendor":
        # Vendor sees their data only
        data = get_vendor_dashboard(db, current_user.id)
    else:
        # Customer sees their orders
        data = get_customer_dashboard(db, current_user.id)

    return data

Technical Reference

For detailed API documentation of authentication classes and methods: