> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snackbase.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication Model

> Multi-tenant authentication flows, token management, API keys, OAuth/SAML integration

SnackBase provides a comprehensive authentication system designed for multi-tenant applications. This guide explains authentication flows, token management, API keys, multi-account users, email verification, OAuth/SAML integration, and security considerations.

## Overview

SnackBase authentication is built for **enterprise multi-account scenarios**:

| Feature                    | Description                                           |
| -------------------------- | ----------------------------------------------------- |
| **Account-Scoped Users**   | Users belong to specific accounts                     |
| **Multi-Account Support**  | Same email can exist in multiple accounts             |
| **Per-Account Passwords**  | Different passwords per (email, account) tuple        |
| **JWT Tokens**             | Access tokens (1 hour) + Refresh tokens (7 days)      |
| **Token Rotation**         | Refresh token rotation on each use with revocation    |
| **API Keys**               | Service authentication with hashed keys               |
| **Email Verification**     | Required for login, tokens expire in 1 hour           |
| **Multi-Provider**         | Support for Password, OAuth, and SAML providers       |
| **Identity Linking**       | Link local accounts with external provider identities |
| **Timing-Safe Comparison** | Password verification resistant to timing attacks     |
| **Hierarchical Config**    | System-level and account-level provider settings      |

## User Identity Model

In SnackBase, a user's identity is defined by a **tuple**:

```
(email, account_id) = unique user identity
```

This means:

* `alice@acme.com` in account `AB1001` = User Identity #1
* `alice@acme.com` in account `XY2048` = User Identity #2
* These are **different users** with different passwords

## Account Registration

Account registration creates a new tenant/workspace in SnackBase.

### Account ID Format

Accounts use **two identifiers**:

```python theme={null}
# Example account
{
  "id": "550e8400-e29b-41d4-a716-446655440000",  # UUID (primary key)
  "account_code": "AB1001",                       # Human-readable code
  "slug": "acme-corp",                            # URL-friendly identifier
  "name": "Acme Corp"                             # Display name
}
```

**Properties:**

* **id (UUID)**: Primary key, immutable, globally unique
* **account\_code (XX####)**: Human-readable format for display
  * Format: 2 letters + 4 digits (e.g., AB1001, XY2048)
  * Sequential generation for easy reference
  * Used in UI and exports
* **slug**: URL-friendly identifier for login
* **name**: Display name (not unique)

## User Registration

User registration creates a new user within a specific account.

### Registration Flow

```
┌──────────────┐
│ User fills   │
│ registration │
│ form         │
└──────┬───────┘
       │
       ▼
┌─────────────────────────────────┐
│ POST /api/v1/auth/register      │
│ {                               │
│   "account": "acme-corp",        │
│   "email": "alice@acme.com",     │
│   "password": "SecurePass123!",  │
│   "name": "Alice Johnson"        │
│ }                               │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Resolve account by slug      │
│    "acme-corp" → account_id     │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 2. Validate email uniqueness    │
│    (within account)             │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 3. Validate password strength   │
│    - Min 8 chars                │
│    - Uppercase, lowercase       │
│    - Number, special char       │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 4. Hash password (Argon2id)     │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 5. Create user record           │
│ - id: user_abc123               │
│ - account_id: <uuid>            │
│ - email: alice@acme.com         │
│ - password_hash: <argon2 hash>  │
│ - email_verified: false         │
│ - auth_provider: "password"     │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 6. Generate verification token  │
│    - SHA-256 hash               │
│    - 1 hour expiration          │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 7. Send verification email      │
│    To: alice@acme.com           │
└─────────────────────────────────┘
```

### Email Uniqueness

Email uniqueness is **scoped to account**:

```
✅ ALLOWED:
Account AB1001: alice@acme.com
Account XY2048: alice@acme.com  (Same email, different account)

❌ NOT ALLOWED:
Account AB1001: alice@acme.com
Account AB1001: alice@acme.com  (Duplicate within account)
```

## Email Verification

Email verification is **required** before users can log in to their accounts.

### Verification Flow

```
┌─────────────────────────────────┐
│ User completes registration     │
│ Account created                 │
│ email_verified: false           │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ System generates verification   │
│ token (random 32-byte string)   │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Token hashed with SHA-256       │
│ Stored in email_verifications   │
│ - token_hash: <sha256>          │
│ - expires_at: now() + 1 hour    │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Verification email sent         │
│ Subject: Verify your email      │
│ Contains verification link      │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ User clicks link                │
│ GET /auth/verify-email?token=...│
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Hash provided token          │
│    SHA-256(token)               │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 2. Lookup token_hash in DB      │
│    Check not expired            │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 3. Update user record           │
│    - email_verified: true       │
│    - email_verified_at: now()   │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 4. Delete verification token    │
│    (single-use only)            │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 5. Return success response      │
│    User can now login           │
└─────────────────────────────────┘
```

### Verification Token Model

```python theme={null}
# Email Verification Token
{
  "id": "ev_abc123",
  "user_id": "user_xyz789",
  "token_hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",  # SHA-256
  "expires_at": "2025-01-01T01:00:00Z",  # 1 hour from creation
  "created_at": "2025-01-01T00:00:00Z"
}
```

**Security Properties:**

* Tokens are **hashed** with SHA-256 before storage (never stored in plaintext)
* Tokens **expire** after 1 hour
* Tokens are **single-use** (deleted after verification)
* Token hash uses **constant-time comparison** to prevent timing attacks

### Login Requirement

Users **cannot login** until their email is verified:

```python theme={null}
# Login check
if not user.email_verified:
    raise HTTPException(
        status_code=401,
        detail="Email not verified. Please check your inbox."
    )
```

## Login Flow

Login authenticates a user and issues JWT tokens.

### Login Process

```
┌──────────────┐
│ User enters  │
│ credentials: │
│ - account    │
│ - email      │
│ - password   │
└──────┬───────┘
       │
       ▼
┌─────────────────────────────────┐
│ POST /api/v1/auth/login         │
│ {                               │
│   "account": "acme-corp",        │
│   "email": "alice@acme.com",     │
│   "password": "SecurePass123!"  │
│ }                               │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Resolve account by slug      │
│    "acme-corp" → account_id     │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 2. Find user by (email, account)│
│    WHERE email = ?              │
│      AND account_id = ?         │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 3. Check email verification     │
│    if not verified: 401 Error   │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 4. Timing-safe password verify  │
│    argon2.verify(password_hash, │
│                provided_password)│
│    (uses dummy hash if no user) │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 5. Generate tokens              │
│    - Access token (1 hour)      │
│    - Refresh token (7 days)     │
│    - Store refresh token hash   │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 6. Return tokens                │
│ {                               │
│   "access_token": "...",        │
│   "refresh_token": "...",       │
│   "token_type": "bearer",       │
│   "user": { ... }               │
│ }                               │
└─────────────────────────────────┘
```

### Timing-Safe Password Comparison

SnackBase uses **timing-safe comparison** to prevent timing attacks:

```python theme={null}
# ❌ VULNERABLE: Regular comparison (timing leak)
if user.password_hash == provided_password:
    # Attacker can measure time to guess password

# ✅ SECURE: Timing-safe comparison
# Also uses dummy hash for non-existent users
if argon2.verify(user.password_hash, provided_password):
    # Constant time regardless of match
```

## Token Management

SnackBase uses **JWT (JSON Web Tokens)** with access and refresh tokens, with true token rotation for enhanced security.

### Token Types

| Token Type        | Lifetime | Purpose              | Storage                         | Database |
| ----------------- | -------- | -------------------- | ------------------------------- | -------- |
| **Access Token**  | 1 hour   | API requests         | localStorage/memory             | No       |
| **Refresh Token** | 7 days   | Get new access token | HttpOnly cookie or localStorage | Yes      |

### Access Token Structure

```json theme={null}
{
  "sub": "user_abc123",           // Subject (user ID)
  "account_id": "550e8400-...",   // Account context (UUID)
  "email": "alice@acme.com",      // User email
  "role": "admin",                // User role
  "exp": 1704067200,              // Expiration timestamp
  "iat": 1704063600               // Issued at timestamp
}
```

### Refresh Token Structure

```json theme={null}
{
  "sub": "user_abc123",           // Subject (user ID)
  "account_id": "550e8400-...",   // Account context (UUID)
  "jti": "token_xyz789",          // JWT ID (unique token identifier)
  "exp": 1704668400,              // Expiration timestamp (7 days)
  "iat": 1704063600               // Issued at timestamp
}
```

**The `jti` (JWT ID) claim** uniquely identifies each refresh token and is used to track revocation.

### Token Refresh with Rotation

```bash theme={null}
# Refresh access token
POST /api/v1/auth/refresh
Content-Type: application/json

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

# Response
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",  // NEW!
  "token_type": "bearer"
}
```

**True Token Rotation:**

1. Old refresh token is marked as **revoked** in database
2. New refresh token is generated and stored (hash)
3. Old token cannot be used again (returns 401 if attempted)
4. Each refresh creates a new token in the chain

## OAuth 2.0 Authentication

SnackBase supports OAuth 2.0 / OpenID Connect authentication for popular social and enterprise identity providers.

### Supported OAuth Providers

| Provider      | Description                |
| ------------- | -------------------------- |
| **Google**    | Google Account login       |
| **GitHub**    | GitHub account login       |
| **Microsoft** | Microsoft / Azure AD login |
| **Apple**     | Sign in with Apple         |

### OAuth Flow

```
┌──────────────┐
│ User clicks  │
│ "Login with  │
│ Google"      │
└──────┬───────┘
       │
       ▼
┌─────────────────────────────────┐
│ GET /oauth/google/authorize     │
│ ?account=acme-corp              │
│ &client_state=abc123            │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Generate state token         │
│ 2. Encode RelayState            │
│ 3. Redirect to Google           │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ User authenticates              │
│ with Google                     │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Google redirects back           │
│ GET /oauth/google/callback?     │
│   code=...&                     │
│   state=...&                    │
│   relay_state=...               │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Verify state token           │
│ 2. Decode RelayState            │
│ 3. Exchange code for tokens     │
│ 4. Get user info                │
│ 5. Find or create user          │
│ 6. Update user record           │
│ 7. Generate JWT tokens          │
│ 8. Redirect to client app       │
└─────────────────────────────────┘
```

## SAML 2.0 Authentication

SnackBase supports SAML 2.0 for enterprise single sign-on (SSO) with identity providers like Okta, Azure AD, and other SAML-compliant systems.

### Supported SAML Providers

| Provider     | Description                      |
| ------------ | -------------------------------- |
| **Okta**     | Okta Identity Cloud SSO          |
| **Azure AD** | Microsoft Azure Active Directory |
| **Generic**  | Any SAML 2.0 compliant IdP       |

### SAML Flow

```
┌──────────────┐
│ User clicks  │
│ "Login with  │
│ SSO"         │
└──────┬───────┘
       │
       ▼
┌─────────────────────────────────┐
│ GET /saml/{provider}/sso        │
│ ?account=acme-corp              │
│ &client_state=abc123            │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Resolve SAML config          │
│ 2. Generate SAML request        │
│ 3. Encode RelayState            │
│ 4. Redirect to IdP              │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ User authenticates              │
│ with IdP (e.g., Okta)           │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ IdP posts SAML response         │
│ POST /saml/{provider}/acs       │
│ - SAMLResponse=<base64>         │
│ - RelayState=<base64>           │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ 1. Decode RelayState            │
│ 2. Verify SAML response         │
│ 3. Extract user attributes      │
│ 4. Find or create user          │
│ 5. Update user record           │
│ 6. Generate JWT tokens          │
│ 7. Redirect to client app       │
└─────────────────────────────────┘
```

## Multi-Account Users

SnackBase supports **enterprise multi-account scenarios** where users can belong to multiple accounts.

### User Identity Matrix

```
┌────────────────────┬──────────────┬──────────────┬──────────────┐
│ email              │ account_id   │ password     │ role         │
├────────────────────┼──────────────┼──────────────┼──────────────┤
│ alice@acme.com     │ 550e8400-... │ Password1!   │ admin        │
│ alice@acme.com     │ 660e8400-... │ Password2!   │ viewer       │
│ bob@acme.com       │ 550e8400-... │ Password3!   │ editor       │
│ jane@globex.com    │ 660e8400-... │ Password4!   │ admin        │
└────────────────────┴──────────────┴──────────────┴──────────────┘
```

**Key Points:**

* Same email can exist in multiple accounts
* Each `(email, account_id)` tuple has a unique password
* Users must specify account when logging in

### Login with Account Selection

When logging in, users must specify which account they're accessing:

**Option 1: Account in URL (subdomain)**

```
POST https://acme-corp.snackbase.dev/api/v1/auth/login
{
  "email": "alice@acme.com",
  "password": "Password1!"
}
```

**Option 2: Account in Request Body**

```
POST https://snackbase.dev/api/v1/auth/login
{
  "account": "acme-corp",  // Account slug
  "email": "alice@acme.com",
  "password": "Password1!"
}
```

## API Key Authentication

API keys provide an alternative authentication method designed for service-to-service communication, CLI tools, and integrations where JWT token management is impractical.

### When to Use API Keys

| Use Case                 | Recommended Method          | Reason                                          |
| ------------------------ | --------------------------- | ----------------------------------------------- |
| Browser applications     | JWT (access/refresh tokens) | Token rotation, user session management         |
| Mobile apps              | JWT (access/refresh tokens) | Built-in token refresh, user experience         |
| Service-to-service calls | **API Keys**                | No token refresh needed, long-lived credentials |
| CLI tools                | **API Keys**                | Easy configuration, no session management       |
| Webhooks                 | **API Keys**                | Static credentials for incoming requests        |
| Third-party integrations | **API Keys**                | Simple credential sharing                       |
| IoT devices              | **API Keys**                | Limited token handling capabilities             |

### API Key Format

API keys follow a structured format:

```
sb_sk_<account_code>_<random_32_characters>
```

**Example**: `sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`

**Components:**

* `sb_sk` - SnackBase Secret Key prefix (identifies the key type)
* `AB1234` - Account code (human-readable account identifier)
* `a1b2c3...o5p6` - 32-character cryptographically secure random string

### API Key Authentication Flow

```
┌──────────────┐
│ Admin creates │
│ API key via  │
│ UI or API    │
└──────┬───────┘
       │
       ▼
┌─────────────────────────────────┐
│ POST /api/v1/api-keys/         │
│ {                               │
│   "name": "Production Service", │
│   "description": "Backend API"  │
│ }                               │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ System generates API key:       │
│ - Format: sb_sk_<code>_<random> │
│ - Store SHA-256 hash in DB      │
│ - Return full key (ONCE ONLY)   │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Admin stores key securely:      │
│ - Environment variable          │
│ - Secret manager (Vault, AWS)   │
│ - Configuration file (secured)  │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Service uses API key:           │
│ Authorization: Bearer sb_sk_... │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Server validates:               │
│ 1. Extract key from header      │
│ 2. Hash with SHA-256            │
│ 3. Lookup hash in database      │
│ 4. Check not revoked            │
│ 5. Get associated user/account  │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│ Request processed with user     │
│ context from API key            │
└─────────────────────────────────┘
```

### API Key Data Model

```python theme={null}
# API Key in Database
{
  "id": "ak_abc123xyz",                     # Key ID (public)
  "key_hash": "<SHA-256 hash>",              # Hashed API key (never plaintext)
  "account_id": "550e8400-...",              # Associated account
  "account_code": "AB1234",                  # Human-readable account code
  "created_by": "usr_abc123",                # User who created the key
  "name": "Production Service",              # Human-readable name
  "description": "Backend API for prod",     # Optional description
  "last_used_at": "2026-01-17T15:45:00Z",   # Last authentication timestamp
  "is_revoked": false,                       # Revocation status
  "created_at": "2026-01-01T00:00:00Z",
  "revoked_at": null,                        # Set when revoked
  "revoked_by": null                         # User who revoked it
}
```

**Security Properties:**

* **SHA-256 Hashing**: Keys are hashed before storage (plaintext never persisted)
* **Single-User Association**: Each key is linked to one user in one account
* **Immediate Revocation**: Keys can be revoked instantly
* **Audit Trail**: Tracks creation, last used, and revocation
* **Account Scoping**: Keys are automatically scoped to their account

### Authentication Comparison

| Feature              | API Keys                          | JWT Tokens                        |
| -------------------- | --------------------------------- | --------------------------------- |
| **Use Case**         | Service-to-service, CLI, webhooks | Browser, mobile apps              |
| **Lifetime**         | Indefinite (until revoked)        | Access: 1 hour, Refresh: 7 days   |
| **Storage**          | Server-side (hash)                | Client-side (localStorage/cookie) |
| **Visibility**       | Full key shown only at creation   | Tokens visible in responses       |
| **Rotation**         | Manual (create new, revoke old)   | Automatic (on refresh)            |
| **Revocation**       | Immediate                         | On refresh or logout              |
| **User Context**     | Single user per key               | Can include user, role, account   |
| **Security**         | SHA-256 hashed at rest            | Signed, verifiable signature      |
| **Token Management** | Not applicable                    | Required (refresh mechanism)      |
| **Session Support**  | None (stateless)                  | Yes (with refresh tokens)         |

### Creating API Keys

**Via API**:

```bash theme={null}
# Create API key
curl -X POST http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Service",
    "description": "Backend API for production environment"
  }'

# Response (only time full key is shown)
{
  "id": "ak_abc123xyz",
  "name": "Production Service",
  "description": "Backend API for production environment",
  "key": "sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",  # SAVE THIS!
  "account_id": "AB1234",
  "created_at": "2026-01-17T10:30:00Z"
}
```

**Via Admin UI**:

1. Navigate to Settings → API Keys
2. Click "Create API Key"
3. Enter name and description
4. Copy the displayed key (shown only once)
5. Store securely in your application

### Using API Keys

API keys use the standard `Authorization: Bearer` header:

```bash theme={null}
# Using API key for authentication
curl -X GET http://localhost:8000/api/v1/posts \
  -H "Authorization: Bearer sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

# Creating a record
curl -X POST http://localhost:8000/api/v1/posts \
  -H "Authorization: Bearer sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Hello World",
    "content": "Created with API key"
  }'
```

**In Code**:

```python theme={null}
import requests

# Configure API key
API_KEY = "sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
headers = {"Authorization": f"Bearer {API_KEY}"}

# Make requests
response = requests.get(
    "http://localhost:8000/api/v1/posts",
    headers=headers
)
```

```javascript theme={null}
// Using fetch
const API_KEY = "sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6";

const response = await fetch("http://localhost:8000/api/v1/posts", {
  headers: {
    "Authorization": `Bearer ${API_KEY}`
  }
});
```

### Managing API Keys

**List API Keys**:

```bash theme={null}
curl -X GET http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>"
```

**Response** (metadata only, no full keys):

```json theme={null}
{
  "items": [
    {
      "id": "ak_abc123xyz",
      "name": "Production Service",
      "description": "Backend API",
      "created_at": "2026-01-17T10:30:00Z",
      "last_used_at": "2026-01-17T15:45:00Z",
      "is_revoked": false
    }
  ]
}
```

**Revoke API Key**:

```bash theme={null}
curl -X POST http://localhost:8000/api/v1/api-keys/ak_abc123xyz/revoke \
  -H "Authorization: Bearer <jwt_token>"
```

**Get Key Details**:

```bash theme={null}
curl -X GET http://localhost:8000/api/v1/api-keys/ak_abc123xyz \
  -H "Authorization: Bearer <jwt_token>"
```

### Security Best Practices

**1. Secure Storage**:

```python theme={null}
# ✅ Good: Environment variable
import os
API_KEY = os.environ.get("SNACKBASE_API_KEY")

# ✅ Good: Secret manager
import boto3
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='snackbase/api_key')
API_KEY = response['SecretString']

# ❌ Bad: Hardcoded in source
API_KEY = "sb_sk_AB1234_..."  # NEVER do this

# ❌ Bad: Committed to version control
# .env files should be in .gitignore
```

**2. Key Rotation**:

```bash theme={null}
# 1. Create new key
new_key_response=$(curl -s -X POST http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Rotated Key"}')

# 2. Extract key
new_key=$(echo "$new_key_response" | jq -r '.key')

# 3. Update application configuration
export SNACKBASE_API_KEY="$new_key"

# 4. Verify new key works
curl -H "Authorization: Bearer $new_key" http://localhost:8000/api/v1/auth/me

# 5. Revoke old key
curl -X POST http://localhost:8000/api/v1/api-keys/ak_old_key/revoke \
  -H "Authorization: Bearer <jwt_token>"
```

**3. Scoping and Naming**:

```json theme={null}
{
  "name": "Production - Payment Service",
  "description": "Used by payment processing service in production environment"
}
```

Use descriptive names to identify:

* Environment (Production, Staging, Development)
* Service (Payment Service, Webhook Handler, CLI)
* Purpose (Backup Job, Monitoring Integration)

**4. Monitoring and Auditing**:

```bash theme={null}
# Check for unused keys
curl -X GET http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" | \
  jq '.items[] | select(.last_used_at == null)'

# Check for old keys (not used in 90 days)
# Implement automated monitoring and alerting
```

**5. Revocation on Compromise**:

If an API key is accidentally exposed (committed to repo, logged, etc.):

1. Immediately revoke the compromised key
2. Create a replacement key
3. Update all services using the old key
4. Investigate potential unauthorized access
5. Review audit logs for suspicious activity

### API Key vs User Permissions

API keys inherit the permissions of the user who created them:

```python theme={null}
# Admin user creates API key
# → API key has admin permissions

# Viewer user creates API key
# → API key has viewer permissions

# Key permissions are tied to creating user's role
```

This means:

* Create dedicated service users with minimal required permissions
* Don't use personal admin accounts to create production API keys
* Regularly audit which users have created API keys

### Common Patterns

**Service Authentication**:

```python theme={null}
# Backend service calling SnackBase
import os
import requests

API_KEY = os.environ["SNACKBASE_API_KEY"]
headers = {"Authorization": f"Bearer {API_KEY}"}

def create_post(title, content):
    response = requests.post(
        "http://localhost:8000/api/v1/posts",
        headers=headers,
        json={"title": title, "content": content}
    )
    return response.json()
```

**CLI Tool**:

```bash theme={null}
# Configure CLI with API key
snackbase config set api-key sb_sk_AB1234_...

# CLI uses stored key for all commands
snackbase posts list
snackbase posts create --title "Hello"
```

**Webhook Handler**:

```python theme={null}
# Webhook endpoint validates API key
from fastapi import Header, HTTPException

async def webhook_handler(
    authorization: str = Header(...)
):
    if not authorization.startswith("Bearer sb_sk_"):
        raise HTTPException(401, "Invalid API key")

    # Process webhook
```

## Security Features

### Password Hashing (Argon2id)

SnackBase uses **Argon2id**, the OWASP-recommended password hashing algorithm:

```python theme={null}
import argon2

# Password hasher configuration
hasher = argon2.PasswordHasher(
    time_cost=3,       # Number of iterations
    memory_cost=65536, # Memory in KiB (64 MB)
    parallelism=4,     # Number of threads
    hash_len=32,       # Hash length
    salt_len=16        # Salt length
)

# Hash password
password_hash = hasher.hash("SecurePass123!")
# $argon2id$v=19$m=65536,t=3,p=4$...

# Verify password (timing-safe)
is_valid = hasher.verify(password_hash, "SecurePass123!")
```

### Password Requirements

Default password requirements (configurable):

| Requirement       | Minimum      |
| ----------------- | ------------ |
| Length            | 8 characters |
| Uppercase         | 1 character  |
| Lowercase         | 1 character  |
| Number            | 1 digit      |
| Special character | 1 character  |

### Token Expiration

| Token Type    | Default Lifetime | Configurable Via                        |
| ------------- | ---------------- | --------------------------------------- |
| Access Token  | 1 hour           | `SNACKBASE_ACCESS_TOKEN_EXPIRE_MINUTES` |
| Refresh Token | 7 days           | `SNACKBASE_REFRESH_TOKEN_EXPIRE_DAYS`   |

## Best Practices

### 1. Token Storage

**For Web Applications:**

```javascript theme={null}
// ✅ Recommended: HttpOnly cookies for refresh tokens
// Set-Cookie: refresh_token=<token>; HttpOnly; Secure; SameSite=Strict

// ⚠️ Acceptable: localStorage for access token only
localStorage.setItem("access_token", token);

// ❌ Avoid: localStorage for refresh tokens
localStorage.setItem("refresh_token", token); // Vulnerable to XSS
```

### 2. Token Refresh

Implement proactive token refresh:

```javascript theme={null}
// Refresh token 5 minutes before expiration
const token = parseJwt(access_token);
const expiresAt = token.exp * 1000;
const now = Date.now();
const refreshBefore = 5 * 60 * 1000; // 5 minutes

if (expiresAt - now < refreshBefore) {
  await refreshToken();
}
```

### 3. Handle Token Expiration

```javascript theme={null}
// Axios interceptor for automatic token refresh
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      // Access token expired
      try {
        const newToken = await refreshToken();
        // Retry original request
        return axios.request(error.config);
      } catch {
        // Refresh token expired - redirect to login
        window.location.href = "/login";
      }
    }
    return Promise.reject(error);
  }
);
```

### 4. Logout Properly

```javascript theme={null}
async function logout() {
  // Clear tokens from storage
  localStorage.removeItem("access_token");
  document.cookie = "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

  // Call backend logout to revoke refresh token
  await axios.post("/api/v1/auth/logout");

  // Redirect to login
  window.location.href = "/login";
}
```

### 5. Use HTTPS in Production

Never send tokens over unencrypted connections:

```bash theme={null}
# ❌ Development only
http://localhost:8000

# ✅ Production
https://yourdomain.com
```

## Summary

| Concept                  | Key Takeaway                                                                   |
| ------------------------ | ------------------------------------------------------------------------------ |
| **User Identity**        | Defined by `(email, account_id)` tuple                                         |
| **Account Registration** | Creates new tenant with UUID primary key and `XX####` display code             |
| **User Registration**    | Creates user within specific account, email unique per account                 |
| **Email Verification**   | Required for login, tokens expire in 1 hour, single-use                        |
| **Login Flow**           | Resolve account → Find user → Check verification → Verify password → Issue JWT |
| **Token Management**     | Access token (1 hour) + Refresh token (7 days) with true rotation              |
| **OAuth Authentication** | Redirect → Authorize → Callback → Exchange tokens → Create/update user         |
| **SAML Authentication**  | SSO request → IdP → ACS response → Verify → Create/update user                 |
| **Multi-Account Users**  | Same email can exist in multiple accounts with different passwords             |
| **Security**             | Argon2id hashing, timing-safe comparison, token rotation, HTTPS required       |
| **Configuration**        | Hierarchical: system-level defaults → account-level overrides                  |
