Architecture¶
FastAuth uses a clean, layered architecture that separates concerns and maintains database agnosticism.
Design Principles¶
1. Database Agnostic Core¶
The core business logic has no knowledge of the database layer. All database operations are abstracted through adapter interfaces.
Benefits: - Swap databases without changing business logic - Easy to test with mock adapters - Clear separation of concerns
2. Adapter Pattern¶
Database-specific code lives in adapters that implement abstract base classes.
Flow:
3. Dependency Injection¶
FastAPI's dependency injection system allows easy customization and testing.
Architecture Layers¶
┌─────────────────────────────────────┐
│ FastAPI Routes │ Your application
├─────────────────────────────────────┤
│ FastAuth API Layer │ HTTP handlers
├─────────────────────────────────────┤
│ Core Business Logic │ Database-agnostic
├─────────────────────────────────────┤
│ Adapter Interface │ Abstract base classes
├─────────────────────────────────────┤
│ Adapter Implementation │ SQLAlchemy, MongoDB, etc.
└─────────────────────────────────────┘
Layer Responsibilities¶
FastAPI Routes (Your Application) - Include FastAuth router - Define custom routes - Override dependencies
FastAuth API Layer - HTTP request/response handling - Input validation (Pydantic) - Error handling - Rate limiting
Core Business Logic - Authentication logic - Authorization (RBAC) - Session management - Token management - Password hashing - Email verification - Password reset
Adapter Interface - Abstract base classes - Define required methods - No implementation details
Adapter Implementation - Database-specific code - CRUD operations - Query logic - No business logic
Component Architecture¶
Core Components¶
Users (fastauth/core/users.py)¶
- User creation
- Authentication
- Password verification
Refresh Tokens (fastauth/core/refresh_tokens.py)¶
- Token generation
- Token validation
- Token rotation
Password Reset (fastauth/core/password_reset.py)¶
- Reset request
- Token validation
- Password update
Email Verification (fastauth/core/email_verification.py)¶
- Verification request
- Token validation
- Email confirmation
Roles (fastauth/core/roles.py)¶
- Role creation and management
- Permission creation and assignment
- Role-to-user assignment
- Permission checking
- User authorization
Sessions (fastauth/core/sessions.py)¶
- Session creation and tracking
- Device fingerprinting
- Session activity tracking
- Session revocation
- Inactive session cleanup
OAuth (fastauth/core/oauth.py)¶
- OAuth 2.0 provider integration
- Authorization flow initiation
- Code exchange and token validation
- User account creation and linking
- OAuth account management
- PKCE support for enhanced security
Adapters¶
Base Adapters (fastauth/adapters/base/)¶
Abstract interfaces defining required methods.
UserAdapter:
class UserAdapter(ABC):
@abstractmethod
def get_by_email(self, email: str): ...
@abstractmethod
def create_user(self, *, email: str, hashed_password: str): ...
RoleAdapter:
class RoleAdapter(ABC):
@abstractmethod
def create_role(self, *, name: str, description: str | None = None): ...
@abstractmethod
def assign_role_to_user(self, *, user_id: UUID, role_id: UUID): ...
@abstractmethod
def get_user_permissions(self, user_id: UUID): ...
SessionAdapter:
class SessionAdapter(ABC):
@abstractmethod
def create_session(self, *, user_id: UUID, device: str | None = None,
ip_address: str | None = None, user_agent: str | None = None): ...
@abstractmethod
def get_user_sessions(self, user_id: UUID): ...
@abstractmethod
def delete_session(self, session_id: UUID) -> None: ...
@abstractmethod
def cleanup_inactive_sessions(self, inactive_days: int = 30) -> None: ...
OAuthAccountAdapter:
class OAuthAccountAdapter(ABC):
@abstractmethod
def create(self, *, user_id: UUID, provider: str, provider_user_id: str,
access_token: str, refresh_token: str | None = None, expires_at: datetime | None = None): ...
@abstractmethod
def get_by_provider_user_id(self, provider: str, provider_user_id: str): ...
@abstractmethod
def get_by_user_id(self, user_id: UUID): ...
@abstractmethod
def delete(self, account_id: UUID) -> None: ...
OAuthStateAdapter:
class OAuthStateAdapter(ABC):
@abstractmethod
def create(self, *, state_hash: str, provider: str, redirect_uri: str,
code_verifier: str | None = None, expires_at: datetime, state_data: dict | None = None): ...
@abstractmethod
def get_valid(self, state_hash: str): ...
@abstractmethod
def delete(self, state_hash: str) -> None: ...
SQLAlchemy Implementation (fastauth/adapters/sqlalchemy/)¶
Concrete implementation using SQLModel.
class SQLAlchemyUserAdapter(UserAdapter):
def __init__(self, session: Session):
self.session = session
def get_by_email(self, email: str):
return self.session.exec(
select(User).where(User.email == email)
).first()
API Layer¶
Routes (fastauth/api/auth.py)¶
FastAPI router with authentication endpoints.
@router.post("/register")
def register(payload: RegisterRequest, session: Session = Depends(get_session)):
users = SQLAlchemyUserAdapter(session)
return create_user(users=users, email=payload.email, password=payload.password)
Dependencies (fastauth/api/dependencies.py)¶
Reusable dependencies for route protection.
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
session: Session = Depends(get_session)
) -> User:
# Validate token and return user
Security Components¶
JWT (fastauth/security/jwt.py)¶
- Token creation
- Token validation
- Expiration handling
Password Hashing (fastauth/core/hashing.py)¶
- Argon2 hashing
- Password verification
Rate Limiting (fastauth/security/rate_limit.py)¶
- Attempt tracking
- Window-based limiting
- Automatic cleanup
Email Components¶
Email Factory (fastauth/email/factory.py)¶
Selects appropriate email provider based on configuration.
Providers¶
- Console (development)
- SMTP (production)
- SendGrid (planned)
- AWS SES (planned)
Data Flow¶
Registration Flow¶
1. User submits email/password
↓
2. API validates input (Pydantic)
↓
3. Core checks for existing user (via adapter)
↓
4. Core hashes password
↓
5. Core creates user (via adapter)
↓
6. If email verification required:
- Generate token
- Send email
↓
7. Return user object
Login Flow¶
1. User submits email/password
↓
2. API validates input
↓
3. Rate limiter checks attempts
↓
4. Core retrieves user (via adapter)
↓
5. Core verifies password
↓
6. Core checks email verification
↓
7. Create access token (JWT)
↓
8. Create refresh token (via adapter)
↓
9. Return tokens
Token Refresh Flow¶
1. User submits refresh token
↓
2. Core validates token hash (via adapter)
↓
3. Core checks expiration
↓
4. Core revokes old token (via adapter)
↓
5. Create new access token
↓
6. Create new refresh token (via adapter)
↓
7. Return new tokens
Authorization Flow (RBAC)¶
1. User requests protected resource
↓
2. Validate access token (get user)
↓
3. Check required role/permission (dependency)
↓
4. Core retrieves user roles (via adapter)
↓
5. Core retrieves role permissions (via adapter)
↓
6. Core checks if user has required permission
↓
7. If authorized → Execute route
If unauthorized → Return 403 Forbidden
OAuth 2.0 Authentication Flow¶
Authorization Initiation:
1. User clicks "Sign in with Google"
↓
2. Frontend: POST /oauth/google/authorize
↓
3. Core generates state token (CSRF protection)
- Random token created
- Hashed and stored (via OAuthStateAdapter)
- Expires in 10 minutes
↓
4. Core optionally generates PKCE challenge
- Code verifier (random string)
- Code challenge (SHA-256 hash of verifier)
↓
5. Core builds authorization URL
- Includes client_id, redirect_uri, state
- Includes code_challenge if PKCE enabled
↓
6. Return authorization URL to frontend
↓
7. Frontend redirects user to authorization URL
OAuth Callback:
1. User authenticates with OAuth provider
↓
2. User grants permissions
↓
3. OAuth provider redirects to callback URL
- Includes authorization code
- Includes state token
↓
4. Frontend: POST /oauth/google/callback
- Sends code and state
↓
5. Core validates state token (via OAuthStateAdapter)
- Checks state exists and not expired
- Deletes state token (single-use)
↓
6. Core exchanges code for access token
- Sends code + code_verifier (if PKCE)
- Receives access_token and refresh_token
↓
7. Core fetches user info from OAuth provider
- Uses access_token
- Gets email, provider_user_id, name
↓
8. Core creates or links user account
- If OAuth account exists → return existing user
- If email exists → link OAuth to existing user
- If new user → create user + OAuth account
↓
9. Core encrypts and stores OAuth tokens (via OAuthAccountAdapter)
↓
10. Create session and JWT tokens
↓
11. Return JWT tokens to frontend
Session Management Flow¶
Login/Registration:
1. User successfully authenticates
↓
2. Extract device info from request
- IP address
- User agent
- Device identifier (optional)
↓
3. Core creates session (via adapter)
↓
4. Core updates user's last_login timestamp
↓
5. Return tokens
List Sessions:
1. User requests /sessions
↓
2. Validate access token (get user)
↓
3. Core retrieves user sessions (via adapter)
↓
4. Return session list with metadata
Revoke Session:
1. User requests DELETE /sessions/{session_id}
↓
2. Validate access token (get user)
↓
3. Core verifies session ownership
↓
4. If owned → Delete session (via adapter)
If not owned → Return 404 Not Found
↓
5. Return success
Cleanup Inactive Sessions:
1. Scheduled job triggers (e.g., daily)
↓
2. Core queries sessions (via adapter)
↓
3. Core filters by last_active < cutoff date
↓
4. Core deletes inactive sessions (via adapter)
Security Architecture¶
Password Security¶
Argon2 Hashing: - Industry-recommended algorithm - Memory-hard (resistant to GPU attacks) - Configurable cost parameters
Flow:
Token Security¶
JWT Access Tokens: - Short-lived (30 minutes default) - Stateless - Signed with secret key
Refresh Tokens: - Long-lived (7 days default) - Stored in database (can be revoked) - Hashed before storage - Rotated on use
Rate Limiting¶
Protection Against: - Brute force attacks - Credential stuffing - DoS attacks
Implementation:
Authorization (RBAC)¶
Role-Based Access Control: - Fine-grained authorization - Separation of authentication and authorization - Flexible permission model
Architecture:
Many-to-Many Relationships: - Users can have multiple roles - Roles can have multiple permissions - Users inherit all permissions from their roles
Route Protection:
# Protect by role
@app.get("/admin", dependencies=[Depends(require_role("admin"))])
# Protect by permission
@app.delete("/users/{id}", dependencies=[Depends(require_permission("delete:users"))])
Permission Check:
# Check permission programmatically
has_access = check_permission(
roles=role_adapter,
user_id=user.id,
permission_name="delete:users"
)
SQL Injection Protection¶
All database operations use parameterized queries via SQLModel:
# Safe - parameterized
statement = select(User).where(User.email == email)
# Never do this - vulnerable
query = f"SELECT * FROM users WHERE email = '{email}'"
Extension Points¶
Custom Adapters¶
Implement the adapter interface for any database:
from fastauth.adapters.base import UserAdapter
class MongoDBUserAdapter(UserAdapter):
def __init__(self, db):
self.db = db
def get_by_email(self, email: str):
return self.db.users.find_one({"email": email})
Custom Email Providers¶
Implement the email provider interface:
from fastauth.email.base import EmailProvider
class SendGridProvider(EmailProvider):
def send_verification_email(self, user, token):
# SendGrid implementation
Custom Routes¶
Add your own protected routes:
from fastauth.api.dependencies import get_current_user
@app.get("/profile")
def get_profile(current_user = Depends(get_current_user)):
return {"email": current_user.email}
Testing Architecture¶
Unit Tests¶
Test core logic with mock adapters:
class FakeUserAdapter(UserAdapter):
def __init__(self):
self.users = {}
def create_user(self, email, hashed_password):
user = User(email=email, hashed_password=hashed_password)
self.users[email] = user
return user
Integration Tests¶
Test with real database (SQLite in memory):
@pytest.fixture
def session():
engine = create_engine("sqlite:///:memory:")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
API Tests¶
Test HTTP endpoints with TestClient:
client = TestClient(app)
response = client.post("/auth/register", json={
"email": "test@example.com",
"password": "password123"
})
Performance Considerations¶
Database¶
Connection Pooling: - SQLAlchemy manages connection pools - Configure pool size for your workload
Indexes: - Email (unique) - Token hashes (unique) - User IDs (primary key)
Caching¶
Rate Limiter: - In-memory storage (defaultdict) - Automatic cleanup of old attempts
Sessions: - Database-backed (can be moved to Redis)
Token Generation¶
Secure Random:
- Uses secrets module
- Cryptographically secure
Hashing: - Argon2 is CPU-intensive (by design) - Balance security with performance
Configuration¶
Settings Management¶
Pydantic settings with environment variables:
class Settings(BaseSettings):
jwt_secret_key: str
access_token_expire_minutes: int = 30
model_config = SettingsConfigDict(env_file=".env")
Environment-Based¶
Different settings per environment:
# Development
EMAIL_PROVIDER=console
REQUIRE_EMAIL_VERIFICATION=false
# Production
EMAIL_PROVIDER=smtp
REQUIRE_EMAIL_VERIFICATION=true
Error Handling¶
Exception Hierarchy¶
Exception
├── UserAlreadyExistsError
├── InvalidCredentialsError
├── EmailNotVerifiedError
├── RefreshTokenError
├── PasswordResetError
├── EmailVerificationError
├── RoleNotFoundError
├── PermissionNotFoundError
├── RoleAlreadyExistsError
├── PermissionAlreadyExistsError
├── SessionNotFoundError
├── RateLimitExceeded
└── TokenError
HTTP Error Mapping¶
try:
user = authenticate_user(...)
except InvalidCredentialsError:
raise HTTPException(status_code=401, detail="Invalid credentials")
except EmailNotVerifiedError:
raise HTTPException(status_code=403, detail="Email not verified")
Deployment Considerations¶
Database Migrations¶
FastAuth doesn't include migrations. Use Alembic:
Secret Management¶
Never commit secrets:
# Use environment variables
export JWT_SECRET_KEY=$(openssl rand -hex 32)
# Or secret management service
# AWS Secrets Manager, HashiCorp Vault, etc.
Scaling¶
Horizontal Scaling: - Stateless API (JWT tokens) - Database handles state - Load balancer distributes requests
Database Scaling: - Read replicas for queries - Write to primary - Consider separate auth database
Future Architecture¶
Completed: - ✅ RBAC - Role-based access control with permissions - ✅ Session Management - Track and manage user sessions across devices - ✅ OAuth 2.0 - Third-party authentication with Google (PKCE support) - ✅ Account Management - Password change, email change, account deletion
Planned enhancements:
Multi-Tenancy: - Organization context - Tenant isolation - Per-tenant settings
Two-Factor Authentication: - TOTP support - Backup codes - Trusted devices
Audit Logging: - All auth events - Suspicious activity detection - Compliance requirements