- Add Development URL Quick Reference section to url-routing overview with all login URLs, entry points, and full examples - Replace /shop/ path segments with /storefront/ across 50 docs files - Update file references: shop_pages.py → storefront_pages.py, templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/ - Preserve domain references (orion.shop) and /store/ staff dashboard paths - Archive docs left unchanged (historical) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
19 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
- Store dashboard
- 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 uses a 4-value role enum on the User model to distinguish user types:
User.role: "super_admin" | "platform_admin" | "merchant_owner" | "store_member"
Customer Role (Separate Model)
Access: Public storefront and own account space
Capabilities:
- Browse store storefronts
- Place orders
- Manage their own account and order history
- View order status
- Update profile information
- Can register directly from storefront frontend
Account Creation: Self-registration via storefront frontend (email verification required)
Authentication: Standard JWT authentication (separate Customer model, not User)
Merchant Owner (role="merchant_owner")
Access: Full access to owned store dashboards
Characteristics:
- Has ALL store permissions automatically (no role record needed)
- Ownership determined by
Merchant.owner_user_id User.is_merchant_ownerproperty returnsTrueUser.is_store_userproperty returnsTrueUser.is_owner_of(store_id)checks ownership
Capabilities:
- Manage products and inventory
- Process orders
- View analytics and reports
- Configure storefront settings
- Manage team members (invite, remove, update roles)
- Access store-specific APIs
Account Creation: Created automatically when admin creates a store
Store Member (role="store_member")
Access: Store area based on assigned role permissions
Characteristics:
- Permissions come from
StoreUser.role_id -> Role.permissions User.is_store_userproperty returnsTrue- Must be invited by merchant owner via email
Capabilities: Limited based on assigned role (Manager, Staff, Support, Viewer, Marketing, or custom)
Account Creation: Invited by merchant owner via email
Permissions System: Team members can have granular permissions for different areas (up to 75 permissions)
Super Admin (role="super_admin")
Access: Full platform administration across all platforms
Characteristics:
User.is_super_adminproperty returnsTrue(computed:role == "super_admin")User.is_adminproperty returnsTrue- Can access all platforms without restriction
- Cannot access store portal (blocked by middleware)
Capabilities:
- Manage all stores across all platforms
- Create/manage store 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 existing super admins on the backend
Platform Admin (role="platform_admin")
Access: Platform administration scoped to assigned platforms
Characteristics:
User.is_platform_adminproperty returnsTrue(computed:role == "platform_admin")User.is_adminproperty returnsTrue- Scoped to specific platforms via
AdminPlatformassociation - Cannot access store portal (blocked by middleware)
Capabilities:
- Manage stores within assigned platforms
- Access platform-scoped settings and analytics
- View data within assigned platforms
Account Creation: Created by super admins
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 |
Super admins and platform admins (is_admin) |
Platform administration and store management |
| Store | /store/* |
Merchant owners and store members (is_store_user) |
Store dashboard and storefront management |
| Storefront | /storefront/*, 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 (Super Admin & Platform Admin)
- ❌ Cannot register from frontend
- ✅ Super Admins (
role="super_admin"): Created by existing super admins - ✅ Platform Admins (
role="platform_admin"): Created by super admins - Used for: Platform administration
Store Accounts (Merchant Owner & Store Member)
- ❌ Cannot register from frontend
- ✅ Merchant Owners (
role="merchant_owner"): Automatically created when admin creates a new store - ✅ Store Members (
role="store_member"): Invited by merchant owner via email invitation - Activation: Upon clicking email verification link
Customer Accounts
- ✅ Can register directly on store storefront
- 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_store()
Allows access to store users and admins.
Usage:
@app.get("/store/products")
async def store_products(
current_user: User = Depends(auth_manager.require_store)
):
return {"products": [...]}
Raises: InsufficientPermissionsException if user is not store or admin
require_customer()
Allows access to customer users and admins.
Usage:
@app.get("/storefront/orders")
async def storefront_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.,"super_admin","platform_admin","merchant_owner","store_member")
Returns: A decorator function that:
- Accepts a function as input
- Returns a wrapper that validates the current user's role
- 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("/owner-only")
@auth_manager.require_role("merchant_owner")
async def owner_endpoint(current_user: User):
"""Only users with role='merchant_owner' can access this."""
return {"message": "Merchant owner access granted"}
# Can also be used with other specific roles
@router.get("/super-admin-only")
@auth_manager.require_role("super_admin")
async def super_admin_endpoint(current_user: User):
return {"data": "super admin content"}
Error Response:
{
"detail": "Required role 'merchant_owner' not found. Current role: 'store_member'"
}
Note: For standard access patterns, prefer using the dedicated methods (require_admin(), require_store(), require_customer()) or the computed properties (is_admin, is_store_user) as they provide better error handling and custom exceptions.
create_default_admin_user()
Creates a default super 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:
- Checks if a user with username "admin" already exists
- If not found, creates a new super admin user with:
- Username:
admin - Email:
admin@example.com - Password:
admin123(hashed with bcrypt) - Role:
super_admin - Status: Active
- Username:
- 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="super_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": "merchant_owner", // User role (4-value enum)
"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[Super Admin<br/>role=super_admin] --> B[Full Platform Access]
A --> C[All Platforms]
AA[Platform Admin<br/>role=platform_admin] --> D2[Scoped Platform Access]
AA --> D3[Assigned Platforms Only]
D[Merchant Owner<br/>role=merchant_owner] --> E[Store Dashboard]
D --> F[Team Management]
D --> G[Storefront Settings]
D --> H[All Store Permissions - 75]
I[Store Member<br/>role=store_member] --> E
I --> J[Role-Based Permissions]
K[Customer<br/>separate model] --> L[Storefront Access]
K --> M[Own Orders]
K --> N[Own Profile]
Admin Override: Admin users (is_admin: super admins and platform admins) have access to the admin portal. They cannot access the store portal directly -- these are separate security boundaries enforced by middleware.
Note: is_super_admin is no longer a database column. It is a computed property: User.role == "super_admin". JWT tokens no longer include an is_super_admin claim; derive it from the role claim instead.
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="merchant_owner")
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
- Always hash passwords: Never store plain text passwords
- Use dependency injection: Leverage FastAPI's
Dependsfor auth - Validate tokens: Always validate tokens on protected endpoints
- Check permissions: Verify user has required role/permissions
- Log auth events: Track login, logout, failed attempts
For Operations
- Strong secret keys: Use long, random JWT secret keys
- HTTPS only: Never send tokens over HTTP
- Token expiration: Keep token lifetimes short
- Rotate secrets: Periodically rotate JWT secret keys
- Monitor auth logs: Watch for suspicious activity
For Security
- Rate limiting: Limit auth endpoint requests
- Account lockout: Implement after N failed attempts
- Email verification: Require email confirmation
- Password policies: Enforce strong password requirements
- 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("/stores")
async def get_stores(
current_user: User = Depends(auth_manager.require_admin),
db: Session = Depends(get_db)
):
"""Only admins can list all stores."""
stores = db.query(Store).all()
return {"stores": stores}
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.is_admin:
# Admin (super_admin or platform_admin) sees platform data
data = get_admin_dashboard(db)
elif current_user.is_store_user:
# Store user (merchant_owner or store_member) sees their store data
data = get_store_dashboard(db, current_user.id)
else:
# Customer sees their orders
data = get_customer_dashboard(db, current_user.id)
return data
Related Documentation
- Middleware Stack - System-wide request processing
- Error Handling - Exception handling
- Backend API Reference - Technical AuthManager docs
- Testing Guide - Testing authentication
Technical Reference
For detailed API documentation of authentication classes and methods: