Authentication
Complete authentication system with JWT tokens, social login, 2FA, and invitation-based user creation.
Features
- JWT Authentication - Access tokens (15 min) + refresh tokens (7 days)
- Social Login - Google, Microsoft, Apple OAuth (configurable)
- Two-Factor Authentication - TOTP-based 2FA with authenticator apps
- Invitation System - Invite users to organizations with pre-assigned roles
- Session Management - Logout single session or all sessions
Authentication Flow
┌─────────┐ ┌─────────┐
│ Client │ │ Backend │
└────┬────┘ └────┬────┘
│ POST /api/v1/auth/login │
│ { email, password } │
├────────────────────────────────────────────>│
│ │
│ 200 OK │
│ { access_token, refresh_token, user } │
│<────────────────────────────────────────────┤
│ │
│ GET /api/v1/auth/me │
│ Authorization: Bearer <access_token> │
├────────────────────────────────────────────>│
│ │
│ 200 OK { user } │
│<────────────────────────────────────────────┤
API Endpoints
Register
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe",
"organization_name": "My Company"
}
Returns tokens and user data. The organization_name field is optional - if provided, creates an organization for the user.
The first registered user automatically receives the superadmin role with full permissions.
Login
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"user": {
"id": "uuid",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_verified": false,
"totp_enabled": false,
"role": { "name": "User", "slug": "user", "permissions": {} },
"organization_id": "uuid",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
}
Get Current User
GET /api/v1/auth/me
Authorization: Bearer <access_token>
Refresh Token
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refresh_token": "<refresh_token>"
}
Logout
POST /api/v1/auth/logout
Authorization: Bearer <access_token>
Content-Type: application/json
{
"refresh_token": "<optional>"
}
Blacklists the access token. Include refresh_token to also invalidate it.
Logout All Sessions
POST /api/v1/auth/logout-all
Authorization: Bearer <access_token>
Invalidates all tokens for the user. Use after password changes.
Invitation System
Users can be invited to join with pre-assigned roles and organizations.
Verify Invitation
POST /api/v1/auth/invitation/verify?token=<invitation_token>
Response:
{
"email": "invited@example.com",
"role_name": "Admin",
"organization_name": "My Company"
}
Accept Invitation
POST /api/v1/auth/invitation/accept
Content-Type: application/json
{
"token": "<invitation_token>",
"password": "SecurePass123!"
}
Creates the user account and returns auth tokens.
Password Requirements
Minimum 8 characters by default. Configured in app/api/v1/auth/schemas.py:
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(min_length=8)
For stronger requirements, add a custom validator:
from pydantic import BaseModel, field_validator
import re
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(min_length=8)
@field_validator("password")
@classmethod
def validate_password(cls, v: str) -> str:
if not re.search(r"[A-Z]", v):
raise ValueError("Must contain uppercase")
if not re.search(r"[a-z]", v):
raise ValueError("Must contain lowercase")
if not re.search(r"\d", v):
raise ValueError("Must contain number")
return v
Token Security
| Token | Storage | Lifetime | Purpose |
|---|---|---|---|
| Access | Memory | 15 min | API requests |
| Refresh | httpOnly cookie | 7 days | Get new access tokens |
Revocation:
/auth/logout- Blacklist current tokens/auth/logout-all- Invalidate all user tokens- Blacklist stored in Redis with auto-expiry
Frontend Integration
AuthContext
import { useAuth } from '@/hooks/useAuth';
function MyComponent() {
const { user, login, logout, isLoading } = useAuth();
if (isLoading) return <Spinner />;
return user ? (
<button onClick={logout}>Logout</button>
) : (
<button onClick={() => login(email, password)}>Login</button>
);
}
Protected Routes
import { ProtectedRoute } from '@/components/ProtectedRoute';
export default function DashboardPage() {
return (
<ProtectedRoute>
<h1>Dashboard</h1>
</ProtectedRoute>
);
}
API Client
The API client automatically handles token refresh on 401 responses.
import { api } from '@/lib/api/client';
// Tokens managed automatically
const response = await api.get('/users/me');
Configuration
Environment variables in backend/.env:
# JWT
JWT_SECRET=your-secret-key
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
# OAuth (optional)
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_CLIENT_SECRET=
MICROSOFT_OAUTH_CLIENT_ID=
MICROSOFT_OAUTH_CLIENT_SECRET=
APPLE_OAUTH_CLIENT_ID=
APPLE_OAUTH_TEAM_ID=
APPLE_OAUTH_KEY_ID=
APPLE_OAUTH_PRIVATE_KEY=
Coming Soon
The following features have schemas defined but endpoints are not yet implemented:
- Password Reset - Forgot password flow via email
- Email Verification - Verify email on signup
- OAuth Endpoints - Social login callback handlers
- 2FA Endpoints - Setup, verify, and disable TOTP
Next Steps
- Backend Architecture - Learn the 3-file module pattern
- Billing - Stripe integration