Authentication
Forge provides two authentication modes: Basic JWT Auth and Complete JWT Auth. Both use JSON Web Tokens for stateless authentication.
Authentication Types
Basic JWT Auth
A simple JWT implementation suitable for internal APIs or when you handle email verification separately:
User registration
User login with access token
Protected route access
Current user endpoint
Complete JWT Auth
A full-featured authentication system for production applications:
Everything in Basic, plus:
Email verification after registration
Refresh token rotation
Password reset via email
Multi-device session management
Device tracking and logout
Security Components
Password Hashing
Forge uses Argon2 for password hashing, the winner of the Password Hashing Competition:
from app.core.security import get_password_hash, verify_password
# Hash a password
hashed = get_password_hash("user_password")
# Verify a password
is_valid = verify_password("user_password", hashed)
Password Validation
The PasswordValidator class enforces password strength:
Minimum 8 characters
At least one uppercase letter
At least one lowercase letter
At least one digit
At least one special character
JWT Tokens
The JWTManager handles token creation and validation:
from app.core.security import security_manager
# Create access token
token, expires_at = security_manager.create_access_token({
"sub": user.username,
"user_id": user.id
})
# Create refresh token (Complete Auth only)
refresh_token, expires_at = security_manager.create_refresh_token({
"sub": user.username,
"user_id": user.id
})
# Decode and validate token
payload = security_manager.decode_token(token)
API Endpoints
Basic JWT Auth Endpoints
Method |
Endpoint |
Description |
|---|---|---|
POST |
|
Register new user |
POST |
|
Login and get access token |
GET |
|
Get current user info |
Complete JWT Auth Endpoints
All Basic endpoints, plus:
Method |
Endpoint |
Description |
|---|---|---|
POST |
|
Verify email with code |
POST |
|
Resend verification email |
POST |
|
Refresh access token |
POST |
|
Logout (revoke refresh token) |
POST |
|
Logout all devices |
POST |
|
Request password reset |
POST |
|
Reset password with code |
POST |
|
Change password (authenticated) |
GET |
|
List logged-in devices |
Authentication Flow
Basic Auth Flow
sequenceDiagram
participant U as User
participant A as API
participant DB as Database
U->>A: POST /auth/register
A->>DB: Create user (active)
A-->>U: User created
U->>A: POST /auth/login
A->>DB: Validate credentials
A-->>U: Access token
U->>A: GET /users/me (with token)
A->>A: Validate token
A-->>U: User info
Complete Auth Flow
sequenceDiagram
participant U as User
participant A as API
participant DB as Database
participant E as Email
U->>A: POST /auth/register
A->>DB: Create user (unverified)
A->>E: Send verification email
A-->>U: User created
U->>A: POST /auth/verify-email
A->>DB: Mark user verified
A-->>U: Email verified
U->>A: POST /auth/login
A->>DB: Validate credentials
A->>DB: Store refresh token
A-->>U: Access token + Refresh token
Note over U,A: When access token expires
U->>A: POST /auth/refresh
A->>DB: Validate refresh token
A-->>U: New access token
U->>A: POST /auth/logout
A->>DB: Revoke refresh token
A-->>U: Logged out
Password Reset Flow
sequenceDiagram
participant U as User
participant A as API
participant DB as Database
participant E as Email
U->>A: POST /auth/forgot-password
A->>DB: Generate reset code
A->>E: Send reset email
A-->>U: Email sent
U->>A: POST /auth/reset-password
A->>DB: Validate code & update password
A-->>U: Password reset
Using Authentication in Routes
Protecting Routes
Use the get_current_user dependency:
from fastapi import Depends
from app.core.deps import get_current_user
from app.models.user import User
@router.get("/protected")
async def protected_route(current_user: User = Depends(get_current_user)):
return {"message": f"Hello, {current_user.username}"}
Optional Authentication
For routes that work with or without authentication:
from typing import Optional
@router.get("/public")
async def public_route(
current_user: Optional[User] = Depends(get_current_user_optional)
):
if current_user:
return {"message": f"Hello, {current_user.username}"}
return {"message": "Hello, guest"}
Email Configuration
Complete JWT Auth requires email configuration for verification and password reset:
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
EMAIL_USE_TLS=true
EMAIL_FROM_NAME="My App"
EMAIL_FROM_EMAIL=noreply@myapp.com
EMAIL_EXPIRATION=3600 # Verification code expiry in seconds
Email Templates
Email templates are stored in static/email_template/:
verification.html- Email verificationpassword_reset.html- Password resetwelcome.html- Welcome email after verification
Templates use Jinja2 syntax with variables like {{ username }}, {{ code }}, {{ expiration_minutes }}.
JWT Configuration
JWT_SECRET_KEY=your-secret-key-at-least-32-characters
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRATION=1800 # 30 minutes
JWT_REFRESH_TOKEN_EXPIRATION=86400 # 24 hours (Complete Auth)
JWT_ISSUER=my-app
JWT_AUDIENCE=my-app_users
Token Payload
Access tokens contain:
{
"sub": "username",
"user_id": 1,
"exp": 1234567890,
"iss": "my-app",
"aud": "my-app_users",
"token_type": "access"
}
Database Models
User Model
class User(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True)
username: str = Field(unique=True, index=True)
email: str = Field(unique=True, index=True)
hashed_password: str
is_active: bool = Field(default=True)
is_verified: bool = Field(default=False) # Complete Auth only
is_superuser: bool = Field(default=False)
created_at: datetime
updated_at: datetime
last_login_at: Optional[datetime] # Complete Auth only
RefreshToken Model (Complete Auth)
class RefreshToken(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True)
user_id: int = Field(foreign_key="users.id")
token: str = Field(unique=True, index=True)
expires_at: datetime
is_revoked: bool = Field(default=False)
device_name: Optional[str]
device_type: Optional[str]
ip_address: Optional[str]
user_agent: Optional[str]
created_at: datetime
last_used_at: Optional[datetime]
VerificationCode Model (Complete Auth)
class VerificationCode(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True)
user_id: int = Field(foreign_key="users.id")
code: str = Field(index=True)
code_type: str # "email_verification" or "password_reset"
expires_at: datetime
is_used: bool = Field(default=False)
created_at: datetime
Security Best Practices
Use strong JWT secrets: At least 32 characters, randomly generated
Keep token expiration short: 15-30 minutes for access tokens
Rotate refresh tokens: Issue new refresh token on each use
Use HTTPS: Always in production
Validate email: Require email verification before allowing login
Rate limit: Implement rate limiting on auth endpoints
Log auth events: Track login attempts, password changes