Authentication

Complete authentication system with JWT tokens, social login, 2FA, and invitation-based user creation.

Features

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.

💡 First User = Superadmin

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)
💡 Enhanced Password Validation

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(&quot;password&quot;)
@classmethod
def validate_password(cls, v: str) -&gt; str:
    if not re.search(r&quot;[A-Z]&quot;, v):
        raise ValueError(&quot;Must contain uppercase&quot;)
    if not re.search(r&quot;[a-z]&quot;, v):
        raise ValueError(&quot;Must contain lowercase&quot;)
    if not re.search(r&quot;\d&quot;, v):
        raise ValueError(&quot;Must contain number&quot;)
    return v

Token Security

Token Storage Lifetime Purpose
Access Memory 15 min API requests
Refresh httpOnly cookie 7 days Get new access tokens

Revocation:

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:

Next Steps