Authentication Guide
Forge provides flexible JWT-based authentication with three modes: None, Basic, and Complete. This guide covers setup, usage, and customization.
Authentication Modes
None
No authentication system. Choose this when:
Building a public API
Implementing custom authentication
Authentication is handled by external service
Basic
JWT token authentication with core features:
User registration
User login
Password hashing (bcrypt)
Token-based API access
User management endpoints
Complete
Everything in Basic, plus:
Email verification
Password reset functionality
Email templates
Token refresh mechanism
Resend verification email
Generated Components
Models
User Model (app/models/user.py):
class User(SQLModel, table=True):
__tablename__ = "users"
id: int | None = Field(default=None, primary_key=True)
email: str = Field(unique=True, index=True)
username: str = Field(unique=True, index=True)
hashed_password: str
is_active: bool = Field(default=True)
is_verified: bool = Field(default=False) # Complete mode only
created_at: datetime = Field(default_factory=datetime.utcnow)
Token Model (app/models/token.py):
class Token(SQLModel, table=True):
__tablename__ = "tokens"
id: int | None = Field(default=None, primary_key=True)
user_id: int = Field(foreign_key="users.id")
token: str = Field(unique=True, index=True)
token_type: str # "access", "refresh", "verification", "reset"
expires_at: datetime
created_at: datetime = Field(default_factory=datetime.utcnow)
Schemas
User Schemas (app/schemas/user.py):
class UserCreate(BaseModel):
email: EmailStr
username: str
password: str
class UserLogin(BaseModel):
username: str
password: str
class UserResponse(BaseModel):
id: int
email: str
username: str
is_active: bool
is_verified: bool # Complete mode only
created_at: datetime
Token Schemas (app/schemas/token.py):
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
refresh_token: str | None = None # Complete mode only
class TokenData(BaseModel):
user_id: int
username: str
API Endpoints
Basic Mode Endpoints
Register:
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"username": "johndoe",
"password": "SecurePass123!"
}
Login:
POST /api/v1/auth/login
Content-Type: application/json
{
"username": "johndoe",
"password": "SecurePass123!"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer"
}
Get Current User:
GET /api/v1/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Update Current User:
PUT /api/v1/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{
"email": "newemail@example.com"
}
Complete Mode Additional Endpoints
Verify Email:
POST /api/v1/auth/verify-email
Content-Type: application/json
{
"token": "verification_token_here"
}
Resend Verification:
POST /api/v1/auth/resend-verification
Content-Type: application/json
{
"email": "user@example.com"
}
Forgot Password:
POST /api/v1/auth/forgot-password
Content-Type: application/json
{
"email": "user@example.com"
}
Reset Password:
POST /api/v1/auth/reset-password
Content-Type: application/json
{
"token": "reset_token_here",
"new_password": "NewSecurePass123!"
}
Refresh Token:
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refresh_token": "refresh_token_here"
}
Security Configuration
JWT Settings
Configuration in app/core/settings.py:
class Settings(BaseSettings):
# JWT Configuration
SECRET_KEY: str = "your-secret-key-here" # Change in production!
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7 # Complete mode only
# Email Configuration (Complete mode only)
SMTP_HOST: str = "smtp.gmail.com"
SMTP_PORT: int = 587
SMTP_USER: str = ""
SMTP_PASSWORD: str = ""
EMAILS_FROM_EMAIL: str = ""
EMAILS_FROM_NAME: str = "My API"
Password Hashing
Uses bcrypt for secure password hashing:
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
Token Generation
from datetime import datetime, timedelta
import jwt
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Using Authentication
Protecting Endpoints
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}!"}
Requiring Verified Users (Complete Mode)
from app.core.deps import get_current_verified_user
@router.get("/verified-only")
async def verified_route(
current_user: User = Depends(get_current_verified_user)
):
return {"message": "You are verified!"}
Optional Authentication
from app.core.deps import get_current_user_optional
@router.get("/optional-auth")
async def optional_auth_route(
current_user: User | None = Depends(get_current_user_optional)
):
if current_user:
return {"message": f"Hello {current_user.username}!"}
return {"message": "Hello guest!"}
Email Configuration (Complete Mode)
SMTP Setup
Update .env.development:
# Gmail Example
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
EMAILS_FROM_EMAIL=your-email@gmail.com
EMAILS_FROM_NAME=My API
# SendGrid Example
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASSWORD=your-sendgrid-api-key
EMAILS_FROM_EMAIL=noreply@yourdomain.com
EMAILS_FROM_NAME=My API
Gmail Setup
Enable 2-Factor Authentication
Generate App Password:
Go to Google Account Settings
Security → 2-Step Verification → App passwords
Generate password for “Mail”
Use app password in
SMTP_PASSWORD
Email Templates
Located in static/email_template/:
Verification Email (verification_email.html):
<!DOCTYPE html>
<html>
<head>
<title>Verify Your Email</title>
</head>
<body>
<h1>Welcome to {{ app_name }}!</h1>
<p>Please verify your email by clicking the link below:</p>
<a href="{{ verification_url }}">Verify Email</a>
</body>
</html>
Password Reset Email (reset_password_email.html):
<!DOCTYPE html>
<html>
<head>
<title>Reset Your Password</title>
</head>
<body>
<h1>Password Reset Request</h1>
<p>Click the link below to reset your password:</p>
<a href="{{ reset_url }}">Reset Password</a>
</body>
</html>
Client Integration
JavaScript/TypeScript
// Register
const register = async (email: string, username: string, password: string) => {
const response = await fetch('http://localhost:8000/api/v1/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, username, password })
});
return response.json();
};
// Login
const login = async (username: string, password: string) => {
const response = await fetch('http://localhost:8000/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
localStorage.setItem('access_token', data.access_token);
return data;
};
// Authenticated Request
const getProfile = async () => {
const token = localStorage.getItem('access_token');
const response = await fetch('http://localhost:8000/api/v1/users/me', {
headers: { 'Authorization': `Bearer ${token}` }
});
return response.json();
};
Python Client
import requests
# Register
response = requests.post(
'http://localhost:8000/api/v1/auth/register',
json={
'email': 'user@example.com',
'username': 'johndoe',
'password': 'SecurePass123!'
}
)
# Login
response = requests.post(
'http://localhost:8000/api/v1/auth/login',
json={'username': 'johndoe', 'password': 'SecurePass123!'}
)
token = response.json()['access_token']
# Authenticated Request
response = requests.get(
'http://localhost:8000/api/v1/users/me',
headers={'Authorization': f'Bearer {token}'}
)
user = response.json()
Customization
Adding Custom Fields
Edit app/models/user.py:
class User(SQLModel, table=True):
__tablename__ = "users"
# Existing fields...
# Add custom fields
first_name: str | None = None
last_name: str | None = None
phone: str | None = None
avatar_url: str | None = None
Create migration:
alembic revision --autogenerate -m "Add user profile fields"
alembic upgrade head
Custom Authentication Logic
Override in app/services/auth.py:
async def authenticate_user(
db: AsyncSession,
username: str,
password: str
) -> User | None:
# Add custom logic here
# Example: Check if user is banned
user = await get_user_by_username(db, username)
if not user or user.is_banned:
return None
if not verify_password(password, user.hashed_password):
return None
return user
Rate Limiting
Add rate limiting to auth endpoints:
from app.core.decorators import rate_limit
@router.post("/login")
@rate_limit(max_requests=5, window_seconds=60)
async def login(user_data: UserLogin, db: AsyncSession = Depends(get_db)):
# Login logic...
Security Best Practices
Production Checklist
Change
SECRET_KEYto a strong random valueUse HTTPS in production
Set secure cookie flags
Implement rate limiting
Add CORS configuration
Enable password complexity requirements
Implement account lockout after failed attempts
Log authentication events
Regular security audits
Keep dependencies updated
Password Requirements
Add validation in app/schemas/user.py:
from pydantic import validator
class UserCreate(BaseModel):
email: EmailStr
username: str
password: str
@validator('password')
def password_strength(cls, v):
if len(v) < 8:
raise ValueError('Password must be at least 8 characters')
if not any(c.isupper() for c in v):
raise ValueError('Password must contain uppercase letter')
if not any(c.islower() for c in v):
raise ValueError('Password must contain lowercase letter')
if not any(c.isdigit() for c in v):
raise ValueError('Password must contain digit')
return v
Token Security
# Rotate tokens regularly
# Invalidate tokens on password change
# Store refresh tokens securely
# Use short expiration times
# Implement token blacklist for logout
Troubleshooting
“Invalid credentials” Error
Check username/password are correct
Verify user exists in database
Check password hashing is working
“Token expired” Error
Token has exceeded
ACCESS_TOKEN_EXPIRE_MINUTESUse refresh token to get new access token (Complete mode)
Re-login to get new token
Email Not Sending (Complete Mode)
Check SMTP credentials in
.envVerify SMTP server allows connections
Check spam folder
Review application logs for errors
“User not verified” Error (Complete Mode)
User must verify email before accessing protected routes
Resend verification email
Check email delivery
See Also
Configuration Options - Auth configuration
Database Setup - User model and migrations
Testing Guide - Testing authentication
Deployment Guide - Production security