28 KiB
Authentication System Documentation
Version: 1.0 Last Updated: November 2025 Audience: Development Team & API Consumers
Table of Contents
- System Overview
- Architecture
- Authentication Contexts
- Implementation Guide
- API Reference
- Security Model
- Testing Guidelines
- Troubleshooting
- Best Practices
System Overview
The Wizamart platform uses a context-based authentication system with three isolated security domains:
- Admin Portal - Platform administration and management
- Vendor Portal - Multi-tenant shop management
- Customer Shop - Public storefront and customer accounts
Each context uses dual authentication supporting both cookie-based (for HTML pages) and header-based (for API calls) authentication with complete isolation between contexts.
Key Features
- Cookie Path Isolation - Separate cookies per context prevent cross-context access
- Role-Based Access Control - Strict enforcement of user roles
- JWT Token Authentication - Stateless, secure token-based auth
- HTTP-Only Cookies - XSS protection for browser sessions
- CSRF Protection - SameSite cookie attribute
- Comprehensive Logging - Full audit trail of authentication events
Architecture
Authentication Flow
┌─────────────────────────────────────────────────────┐
│ Client Request │
└─────────────────┬───────────────────────────────────┘
│
┌───────▼────────┐
│ Route Handler │
└───────┬────────┘
│
┌───────▼────────────────────────────────┐
│ Authentication Dependency │
│ (from app/api/deps.py) │
└───────┬────────────────────────────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌───▼───┐ ┌────▼────┐ ┌───▼────┐
│Cookie │ │ Header │ │ None │
└───┬───┘ └────┬────┘ └───┬────┘
│ │ │
└────────┬───┴────────────┘
│
┌──────▼───────┐
│ Validate JWT │
└──────┬───────┘
│
┌──────▼──────────┐
│ Check User Role │
└──────┬──────────┘
│
┌────────┴─────────┐
│ │
┌───▼────┐ ┌─────▼──────┐
│Success │ │ Auth Error │
│Return │ │ 401/403 │
│User │ └────────────┘
└────────┘
Cookie Isolation
Each authentication context uses a separate cookie with path restrictions:
| Context | Cookie Name | Cookie Path | Access Scope |
|---|---|---|---|
| Admin | admin_token |
/admin |
Admin routes only |
| Vendor | vendor_token |
/vendor |
Vendor routes only |
| Customer | customer_token |
/shop |
Shop routes only |
Browser Behavior:
- When requesting
/admin/*, browser sendsadmin_tokencookie only - When requesting
/vendor/*, browser sendsvendor_tokencookie only - When requesting
/shop/*, browser sendscustomer_tokencookie only
This prevents cookie leakage between contexts.
Authentication Contexts
1. Admin Context
Routes: /admin/*
Role: admin
Cookie: admin_token (path=/admin)
Purpose: Platform administration, vendor management, system configuration.
Access Control:
- ✅ Admin users only
- ❌ Vendor users blocked
- ❌ Customer users blocked
Login Endpoint:
POST /api/v1/admin/auth/login
Example Request:
curl -X POST http://localhost:8000/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
Example Response:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"role": "admin",
"is_active": true
}
}
Additionally, sets cookie:
Set-Cookie: admin_token=<JWT>; Path=/admin; HttpOnly; Secure; SameSite=Lax
2. Vendor Context
Routes: /vendor/*
Role: vendor
Cookie: vendor_token (path=/vendor)
Purpose: Vendor shop management, product catalog, orders, team management.
Access Control:
- ❌ Admin users blocked (admins use admin portal for vendor management)
- ✅ Vendor users (owners and team members)
- ❌ Customer users blocked
Login Endpoint:
POST /api/v1/vendor/auth/login
Example Request:
curl -X POST http://localhost:8000/api/v1/vendor/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"vendor_owner","password":"vendor123"}'
Example Response:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 2,
"username": "vendor_owner",
"email": "owner@vendorshop.com",
"role": "vendor",
"is_active": true
},
"vendor": {
"id": 1,
"vendor_code": "ACME",
"name": "ACME Store"
},
"vendor_role": "owner"
}
Additionally, sets cookie:
Set-Cookie: vendor_token=<JWT>; Path=/vendor; HttpOnly; Secure; SameSite=Lax
3. Customer Context
Routes: /shop/account/* (authenticated), /shop/* (public)
Role: customer
Cookie: customer_token (path=/shop)
Purpose: Product browsing (public), customer accounts, orders, profile management.
Access Control:
- Public Routes (
/shop/products,/shop/cart, etc.):- ✅ Anyone can access (no authentication)
- Account Routes (
/shop/account/*):- ❌ Admin users blocked
- ❌ Vendor users blocked
- ✅ Customer users only
Login Endpoint:
POST /api/v1/public/vendors/{vendor_id}/customers/login
Example Request:
curl -X POST http://localhost:8000/api/v1/public/vendors/1/customers/login \
-H "Content-Type: application/json" \
-d '{"username":"customer","password":"customer123"}'
Example Response:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 100,
"email": "customer@example.com",
"customer_number": "CUST-001",
"is_active": true
}
}
Additionally, sets cookie:
Set-Cookie: customer_token=<JWT>; Path=/shop; HttpOnly; Secure; SameSite=Lax
Implementation Guide
Module Structure
app/api/
├── deps.py # Authentication dependencies
├── v1/
├── admin/
│ └── auth.py # Admin authentication endpoints
├── vendor/
│ └── auth.py # Vendor authentication endpoints
└── public/vendors/
└── auth.py # Customer authentication endpoints
For HTML Pages (Server-Rendered)
Use the *_from_cookie_or_header functions for pages that users navigate to:
from fastapi import APIRouter, Request, Depends
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from app.api.deps import (
get_current_admin_from_cookie_or_header,
get_current_vendor_from_cookie_or_header,
get_current_customer_from_cookie_or_header,
get_db
)
from models.database.user import User
router = APIRouter()
# Admin page
@router.get("/admin/dashboard", response_class=HTMLResponse)
async def admin_dashboard(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("admin/dashboard.html", {
"request": request,
"user": current_user
})
# Vendor page
@router.get("/vendor/{vendor_code}/dashboard", response_class=HTMLResponse)
async def vendor_dashboard(
request: Request,
vendor_code: str,
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("vendor/dashboard.html", {
"request": request,
"user": current_user,
"vendor_code": vendor_code
})
# Customer account page
@router.get("/shop/account/dashboard", response_class=HTMLResponse)
async def customer_dashboard(
request: Request,
current_user: User = Depends(get_current_customer_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("shop/account/dashboard.html", {
"request": request,
"user": current_user
})
For API Endpoints (JSON Responses)
Use the *_api functions for API endpoints to enforce header-based authentication:
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import (
get_current_admin_api,
get_current_vendor_api,
get_current_customer_api,
get_db
)
from models.database.user import User
router = APIRouter()
# Admin API
@router.post("/api/v1/admin/vendors")
def create_vendor(
vendor_data: VendorCreate,
current_user: User = Depends(get_current_admin_api),
db: Session = Depends(get_db)
):
# Only accepts Authorization header (no cookies)
# Better security - prevents CSRF attacks
return {"message": "Vendor created"}
# Vendor API
@router.post("/api/v1/vendor/{vendor_code}/products")
def create_product(
vendor_code: str,
product_data: ProductCreate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
):
return {"message": "Product created"}
# Customer API
@router.post("/api/v1/shop/orders")
def create_order(
order_data: OrderCreate,
current_user: User = Depends(get_current_customer_api),
db: Session = Depends(get_db)
):
return {"message": "Order created"}
For Public Routes (No Authentication)
Simply don't use any authentication dependency:
@router.get("/shop/products")
async def public_products(request: Request):
# No authentication required
return templates.TemplateResponse("shop/products.html", {
"request": request
})
API Reference
Authentication Dependencies
All authentication functions are in app/api/deps.py:
get_current_admin_from_cookie_or_header()
Purpose: Authenticate admin users for HTML pages
Accepts: Cookie (admin_token) OR Authorization header
Returns: User object with role="admin"
Raises:
InvalidTokenException- No token or invalid tokenAdminRequiredException- User is not admin
Usage:
current_user: User = Depends(get_current_admin_from_cookie_or_header)
get_current_admin_api()
Purpose: Authenticate admin users for API endpoints
Accepts: Authorization header ONLY
Returns: User object with role="admin"
Raises:
InvalidTokenException- No token or invalid tokenAdminRequiredException- User is not admin
Usage:
current_user: User = Depends(get_current_admin_api)
get_current_vendor_from_cookie_or_header()
Purpose: Authenticate vendor users for HTML pages
Accepts: Cookie (vendor_token) OR Authorization header
Returns: User object with role="vendor"
Raises:
InvalidTokenException- No token or invalid tokenInsufficientPermissionsException- User is not vendor or is admin
Usage:
current_user: User = Depends(get_current_vendor_from_cookie_or_header)
get_current_vendor_api()
Purpose: Authenticate vendor users for API endpoints
Accepts: Authorization header ONLY
Returns: User object with role="vendor"
Raises:
InvalidTokenException- No token or invalid tokenInsufficientPermissionsException- User is not vendor or is admin
Usage:
current_user: User = Depends(get_current_vendor_api)
get_current_customer_from_cookie_or_header()
Purpose: Authenticate customer users for HTML pages
Accepts: Cookie (customer_token) OR Authorization header
Returns: Customer object
Raises:
InvalidTokenException- No token or invalid tokenInsufficientPermissionsException- User is not customer (admin/vendor blocked)
Usage:
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header)
get_current_customer_api()
Purpose: Authenticate customer users for API endpoints
Accepts: Authorization header ONLY
Returns: Customer object
Raises:
InvalidTokenException- No token or invalid tokenInsufficientPermissionsException- User is not customer (admin/vendor blocked)
Usage:
current_customer: Customer = Depends(get_current_customer_api)
get_current_user()
Purpose: Authenticate any user (no role checking)
Accepts: Authorization header ONLY
Returns: User object (any role)
Raises:
InvalidTokenException- No token or invalid token
Usage:
current_user: User = Depends(get_current_user)
Login Response Format
All login endpoints return:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"role": "admin",
"is_active": true
}
}
Additionally, the response sets an HTTP-only cookie:
- Admin:
admin_token(path=/admin) - Vendor:
vendor_token(path=/vendor) - Customer:
customer_token(path=/shop)
Security Model
Role-Based Access Control Matrix
| User Role | Admin Portal | Vendor Portal | Shop Catalog | Customer Account |
|---|---|---|---|---|
| Admin | ✅ Full | ❌ Blocked | ✅ View | ❌ Blocked |
| Vendor | ❌ Blocked | ✅ Full | ✅ View | ❌ Blocked |
| Customer | ❌ Blocked | ❌ Blocked | ✅ View | ✅ Full |
| Anonymous | ❌ Blocked | ❌ Blocked | ✅ View | ❌ Blocked |
Cookie Security Settings
All authentication cookies use the following security attributes:
response.set_cookie(
key="<context>_token",
value=jwt_token,
httponly=True, # JavaScript cannot access (XSS protection)
secure=True, # HTTPS only in production
samesite="lax", # CSRF protection
max_age=3600, # Matches JWT expiry
path="/<context>" # Path restriction for isolation
)
Token Validation
JWT tokens include:
sub- User IDrole- User role (admin/vendor/customer)exp- Expiration timestampiat- Issued at timestamp
Tokens are validated on every request:
- Extract token from cookie or header
- Verify JWT signature
- Check expiration
- Load user from database
- Verify user is active
- Check role matches route requirements
HTTPS Requirement
Production Environment:
- All cookies have
secure=True - HTTPS required for all authenticated routes
- HTTP requests automatically redirect to HTTPS
Development Environment:
- Cookies have
secure=Falsefor local testing - HTTP allowed (http://localhost:8000)
Testing Guidelines
Manual Testing with Browser
Test Admin Authentication
-
Navigate to admin login:
http://localhost:8000/admin/login -
Login with admin credentials:
- Username:
admin - Password:
admin123(or your configured admin password)
- Username:
-
Verify cookie in DevTools:
- Open DevTools → Application → Cookies
- Look for
admin_tokencookie - Verify
Pathis/admin - Verify
HttpOnlyis checked - Verify
SameSiteisLax
-
Test navigation:
- Navigate to
/admin/dashboard- Should work ✅ - Navigate to
/vendor/TESTVENDOR/dashboard- Should fail (cookie not sent) ❌ - Navigate to
/shop/account/dashboard- Should fail (cookie not sent) ❌
- Navigate to
-
Logout:
POST /api/v1/admin/auth/logout
Test Vendor Authentication
-
Navigate to vendor login:
http://localhost:8000/vendor/{VENDOR_CODE}/login -
Login with vendor credentials
-
Verify cookie in DevTools:
- Look for
vendor_tokencookie - Verify
Pathis/vendor
- Look for
-
Test navigation:
- Navigate to
/vendor/{VENDOR_CODE}/dashboard- Should work ✅ - Navigate to
/admin/dashboard- Should fail ❌ - Navigate to
/shop/account/dashboard- Should fail ❌
- Navigate to
Test Customer Authentication
-
Navigate to customer login:
http://localhost:8000/shop/account/login -
Login with customer credentials
-
Verify cookie in DevTools:
- Look for
customer_tokencookie - Verify
Pathis/shop
- Look for
-
Test navigation:
- Navigate to
/shop/account/dashboard- Should work ✅ - Navigate to
/admin/dashboard- Should fail ❌ - Navigate to
/vendor/{CODE}/dashboard- Should fail ❌
- Navigate to
API Testing with curl
Test Admin API
# Login
curl -X POST http://localhost:8000/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
# Save the access_token from response
# Test authenticated endpoint
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/vendor/TESTVENDOR/products \
-H "Authorization: Bearer <admin_access_token>"
# Should return 403 Forbidden
Test Vendor API
# Login
curl -X POST http://localhost:8000/api/v1/vendor/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"vendor","password":"vendor123"}'
# Test authenticated endpoint
curl http://localhost:8000/api/v1/vendor/TESTVENDOR/products \
-H "Authorization: Bearer <vendor_access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <vendor_access_token>"
# Should return 403 Forbidden
Test Customer API
# Login
curl -X POST http://localhost:8000/api/v1/public/vendors/1/customers/login \
-H "Content-Type: application/json" \
-d '{"username":"customer","password":"customer123"}'
# Test authenticated endpoint with token
curl http://localhost:8000/api/v1/shop/orders \
-H "Authorization: Bearer <customer_access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <customer_access_token>"
# Should return 403 Forbidden
Frontend JavaScript Testing
Login and Store Token
// Admin login
async function loginAdmin(username, password) {
const response = await fetch('/api/v1/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
// Cookie is set automatically
// Optionally store token for API calls
localStorage.setItem('admin_token', data.access_token);
// Redirect to dashboard
window.location.href = '/admin/dashboard';
}
Make API Call with Token
// API call with token
async function fetchVendors() {
const token = localStorage.getItem('admin_token');
const response = await fetch('/api/v1/admin/vendors', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
Page Navigation (Cookie Automatic)
// Just navigate - cookie sent automatically
window.location.href = '/admin/dashboard';
// Browser automatically includes admin_token cookie
Automated Testing
Test Cookie Isolation
import pytest
from fastapi.testclient import TestClient
def test_admin_cookie_not_sent_to_vendor_routes(client: TestClient):
# Login as admin
response = client.post('/api/v1/admin/auth/login', json={
'username': 'admin',
'password': 'admin123'
})
# Try to access vendor route (cookie should not be sent)
response = client.get('/vendor/TESTVENDOR/dashboard')
# Should redirect to login or return 401
assert response.status_code in [302, 401]
def test_vendor_token_blocked_from_admin_api(client: TestClient):
# Login as vendor
response = client.post('/api/v1/vendor/auth/login', json={
'username': 'vendor',
'password': 'vendor123'
})
vendor_token = response.json()['access_token']
# Try to access admin API with vendor token
response = client.get(
'/api/v1/admin/vendors',
headers={'Authorization': f'Bearer {vendor_token}'}
)
# Should return 403 Forbidden
assert response.status_code == 403
Troubleshooting
Common Issues
"Invalid token" error when navigating to pages
Symptom: User is logged in but gets "Invalid token" error
Causes:
- Token expired (default: 1 hour)
- Cookie was deleted
- Wrong cookie being sent
Solution:
- Check cookie expiration in DevTools
- Re-login to get fresh token
- Verify correct cookie exists with correct path
Cookie not being sent to endpoints
Symptom: API calls work with Authorization header but pages don't load
Causes:
- Cookie path mismatch
- Cookie expired
- Wrong domain
Solution:
- Verify cookie path matches route (e.g.,
/admincookie for/admin/*routes) - Check cookie expiration
- Ensure cookie domain matches current domain
"Admin cannot access vendor portal" error
Symptom: Admin user cannot access vendor routes
Explanation: This is intentional security design. Admins have their own portal at /admin. To manage vendors, use admin routes:
- View vendors:
/admin/vendors - Edit vendor:
/admin/vendors/{code}/edit
Admins should not log into vendor portal as this violates security boundaries.
"Customer cannot access admin/vendor routes" error
Symptom: Customer trying to access management interfaces
Explanation: Customers only have access to:
- Public shop routes:
/shop/products, etc. - Their account:
/shop/account/*
Admin and vendor portals are not accessible to customers.
Token works in Postman but not in browser
Cause: Postman uses Authorization header, browser uses cookies
Solution:
- For API testing: Use Authorization header
- For browser testing: Rely on cookies (automatic)
- For JavaScript API calls: Add Authorization header manually
Debugging Tips
Check Cookie in Browser
// In browser console
document.cookie.split(';').forEach(c => console.log(c.trim()));
Decode JWT Token
// In browser console
function parseJwt(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
const token = localStorage.getItem('admin_token');
console.log(parseJwt(token));
Check Server Logs
The authentication system logs all auth events:
INFO: Admin login successful: admin
INFO: Request: GET /admin/dashboard from 127.0.0.1
INFO: Response: 200 for GET /admin/dashboard (0.045s)
Look for:
- Login attempts
- Token validation errors
- Permission denials
Best Practices
For Developers
-
Use the right dependency for the job:
- HTML pages →
get_current_<context>_from_cookie_or_header - API endpoints →
get_current_<context>_api
- HTML pages →
-
Don't mix authentication contexts:
- Admin users should use admin portal
- Vendor users should use vendor portal
- Customers should use shop
-
Always check user.is_active:
if not current_user.is_active: raise UserNotActiveException() -
Use type hints:
def my_route(current_user: User = Depends(get_current_admin_api)): # IDE will have autocomplete for current_user -
Handle exceptions properly:
try: # Your logic except InvalidTokenException: # Handle auth failure except InsufficientPermissionsException: # Handle permission denial
For Frontend
-
Store tokens securely:
- Tokens in localStorage/sessionStorage are vulnerable to XSS
- Prefer using cookies for page navigation
- Only use localStorage for explicit API calls
-
Always send Authorization header for API calls:
const token = localStorage.getItem('token'); fetch('/api/v1/admin/vendors', { headers: { 'Authorization': `Bearer ${token}` } }); -
Handle 401/403 responses:
if (response.status === 401) { // Redirect to login window.location.href = '/admin/login'; } -
Clear tokens on logout:
localStorage.removeItem('token'); // Logout endpoint will clear cookie await fetch('/api/v1/admin/auth/logout', { method: 'POST' });
Security Considerations
- Never log tokens - They're sensitive credentials
- Use HTTPS in production - Required for secure cookies
- Set appropriate token expiration - Balance security vs UX
- Rotate secrets regularly - JWT signing keys
- Monitor failed auth attempts - Detect brute force attacks
Configuration
Environment Variables
# JWT Configuration
JWT_SECRET_KEY=your-secret-key-here
JWT_ALGORITHM=HS256
JWT_EXPIRATION=3600 # 1 hour in seconds
# Environment
ENVIRONMENT=production # or development
# When ENVIRONMENT=production:
# - Cookies use secure=True (HTTPS only)
# - Debug mode is disabled
# - CORS is stricter
Cookie Expiration
Cookies expire when:
- JWT token expires (default: 1 hour)
- User logs out (cookie deleted)
- Browser session ends (for session cookies)
To change expiration:
# In auth endpoint
response.set_cookie(
max_age=7200 # 2 hours
)
AuthManager Class Reference
The AuthManager class handles all authentication and authorization operations including password hashing, JWT token management, and role-based access control.
::: middleware.auth.AuthManager options: show_source: false heading_level: 3 show_root_heading: true show_root_toc_entry: false members: - init - hash_password - verify_password - authenticate_user - create_access_token - verify_token - get_current_user - require_role - require_admin - require_vendor - require_customer - create_default_admin_user
Quick Reference
For a condensed cheat sheet of authentication patterns, see Authentication Quick Reference.
Related Documentation
- RBAC System - Role-based access control and permissions
- Architecture Overview - System-wide authentication architecture
- Backend Development - Backend development guide
- API Reference - Auto-generated API documentation
Document Version: 1.0 Last Updated: November 2025 Maintained By: Backend Team