Skip to content

Authentication & Session Management

4pass uses a layered authentication architecture: JWT tokens for session identity, Argon2id for password verification, per-session CSRF tokens, Cloudflare Turnstile for bot prevention, and an ordered middleware stack that enforces security policies on every request.


JWT Token Architecture

sequenceDiagram
    participant Browser
    participant API as FastAPI
    participant CF as Cloudflare Turnstile
    participant DB as PostgreSQL
    participant Redis as Valkey

    Browser->>API: POST /auth/login<br/>{email, password, turnstile_token}
    API->>CF: Verify Turnstile token
    CF-->>API: ✓ Human verified
    API->>DB: SELECT user WHERE email = ?
    DB-->>API: User record
    API->>API: Argon2id verify(password, hash)
    API->>DB: INSERT user_session<br/>(csrf_token, ip, user_agent)
    DB-->>API: Session created
    API->>API: Sign JWT access token (30 min)<br/>Sign JWT refresh token (30 days)
    API-->>Browser: Set-Cookie: access_token (HttpOnly, Secure, SameSite=strict)<br/>Set-Cookie: refresh_token (HttpOnly, Secure, SameSite=strict)<br/>Body: {user, csrf_token}
    Note over Browser: CSRF token stored<br/>in sessionStorage
    Browser->>API: GET /trading/accounts<br/>Cookie: access_token<br/>X-CSRF-Token: {token}
    API->>API: Verify JWT signature + expiry
    API->>DB: Load user + session
    API->>API: Validate CSRF (hmac.compare_digest)
    API-->>Browser: 200 OK + data

JWT tokens are never exposed to JavaScript. Both access and refresh tokens live in HttpOnly cookies — the frontend doesn't store, read, or transmit tokens manually. This eliminates XSS-based token theft entirely.


Token Management

Property Access Token Refresh Token
Lifetime 30 minutes (ACCESS_TOKEN_EXPIRE_MINUTES) 30 days (REFRESH_TOKEN_EXPIRE_DAYS)
Storage HttpOnly cookie (access_token) HttpOnly cookie (refresh_token)
Algorithm HS256 HS256
SameSite strict strict
Secure true (HTTPS only) true (HTTPS only)
Domain Current domain (COOKIE_DOMAIN) Current domain

Token sources are checked in order:

  1. Cookie (primary) — access_token cookie, used by browser clients
  2. Bearer headerAuthorization: Bearer <token>, used by API clients and Swagger UI

Token refresh flow:

sequenceDiagram
    participant Browser
    participant API as FastAPI
    participant DB as PostgreSQL

    Browser->>API: POST /auth/silent-refresh<br/>Cookie: refresh_token
    API->>API: Verify refresh token JWT
    API->>DB: Validate session still active
    DB-->>API: Session valid
    API->>API: Issue new access token (30 min)
    API->>DB: Optionally rotate refresh token
    API-->>Browser: Set-Cookie: access_token (new)<br/>Set-Cookie: refresh_token (rotated)

Silent Refresh

The /auth/silent-refresh endpoint is excluded from CSRF validation because it relies on the SameSite=strict refresh token cookie for authentication — a CSRF attacker cannot cause the browser to send this cookie cross-origin.


Password Security

4pass uses Argon2id via pwdlib[argon2] — the OWASP-recommended password hashing algorithm for high-value accounts.

Property Value
Algorithm Argon2id (memory-hard + GPU-resistant)
Library pwdlib with PasswordHash.recommended() defaults
Why not bcrypt bcrypt's 72-byte input limit and lower memory cost make it weaker against GPU/ASIC attacks
Why not scrypt Argon2id is the PHC (Password Hashing Competition) winner and has stronger side-channel resistance
from pwdlib import PasswordHash
password_hash = PasswordHash.recommended()

hashed = password_hash.hash("user_password")
verified = password_hash.verify("user_password", hashed)

Session Management

Each login creates a new session record in the user_sessions table:

Field Type Purpose
id UUID Unique session identifier
user_id FK → users Session owner
csrf_token String (32-byte) Per-session CSRF token for synchronizer pattern
ip_address String Client IP at login (via trusted proxy chain)
user_agent String Browser User-Agent at login
created_at Timestamp Session creation time
is_active Boolean false on logout or revocation

Session lifecycle:

  • Creation: On successful login, after Argon2id verification and CAPTCHA check
  • Validation: On every authenticated request — JWT decoded, session loaded from DB, is_active checked
  • Revocation: On explicit logout (POST /auth/logout) or administrative action
  • Expiration: Subscription expiration checks on each request — expired users lose access to premium features

CSRF Protection

4pass implements the Synchronizer Token Pattern with per-session tokens and constant-time comparison.

Property Detail
Pattern Synchronizer Token (per-session, server-validated)
Header X-CSRF-Token
Protected methods POST, PUT, DELETE, PATCH
Token generation 32-byte secrets.token_urlsafe() created per session
Comparison hmac.compare_digest() — constant-time to prevent timing attacks

Excluded paths (no CSRF required):

Path Reason
/webhook/* TradingView webhooks use their own 4-layer authentication
/auth/login, /auth/register No session exists yet
/auth/token OAuth2 standard token endpoint
/auth/forgot-password, /auth/reset-password Pre-authentication flows
/auth/silent-refresh Protected by SameSite=strict cookie
/auth/public-key Public key retrieval (read-only)
/public/* Public endpoints (no session)
/health, /docs, /openapi.json Infrastructure endpoints

Defense in Depth

CSRF protection operates on top of SameSite=strict cookies. Even if a browser bug bypasses SameSite, the CSRF token provides a second line of defense. Conversely, even if CSRF token validation has a flaw, SameSite prevents cross-origin cookie submission.


API Key Authentication

For programmatic access (scripts, integrations), 4pass supports API key authentication as an alternative to JWT sessions.

Property Detail
Generation secrets.token_urlsafe(32) — 256 bits of entropy
Storage SHA-256 hash only — plaintext is shown once at creation and never stored
Lookup Hash incoming key, query api_keys table
Expiration Configurable per key
Tracking last_used_at updated on each use
Revocation Immediate via dashboard or API
Authorization: Bearer <api_key>
    ├── SHA-256(api_key) → lookup in api_keys table
    ├── Check expiration
    ├── Update last_used_at
    └── Resolve user → proceed

CAPTCHA

4pass uses Cloudflare Turnstile — an invisible, privacy-preserving CAPTCHA that requires no user interaction.

Property Detail
Provider Cloudflare Turnstile
UX impact Zero — invisible, no puzzles or checkboxes
Applied to /auth/login, /auth/register
Verification Server-side via Cloudflare API (siteverify endpoint)
Configuration CAPTCHA_ENABLED environment variable
Scanner bypass Security scanners (Wapiti, OWASP ZAP) detected via User-Agent can bypass for testing
sequenceDiagram
    participant Browser
    participant Turnstile as Cloudflare Turnstile
    participant API as FastAPI
    participant CF as Cloudflare API

    Browser->>Turnstile: Load invisible widget
    Turnstile-->>Browser: Challenge token (automatic)
    Browser->>API: POST /auth/login<br/>{email, password, turnstile_token}
    API->>CF: POST siteverify<br/>{secret, token, ip}
    CF-->>API: {success: true}
    API->>API: Proceed with login

IP Whitelist

Users can optionally restrict trading endpoints to specific IP addresses:

  • Configured per-user via the dashboard settings
  • Stored in user_allowed_ips table
  • Checked on webhook and trading requests
  • Violation logging: webhook_ip_blocked audit event with full request context
  • Email alerts: Real-time notification when a request is blocked by the whitelist (rate-limited to 1 per 30 minutes)

When to Use IP Whitelisting

IP whitelisting is recommended for users who send webhooks from a fixed TradingView IP or a dedicated server. It adds a network-level restriction that operates independently of all other authentication layers.


Middleware Stack

Security middleware executes on every request in a fixed order. Each layer applies globally — no route can accidentally bypass protection.

flowchart LR
    REQ["Incoming<br/>Request"] --> CORS["1. CORS<br/>Origin Validation"]
    CORS --> BROWSER["2. BrowserOnly<br/>Blocks curl/Postman"]
    BROWSER --> CSRF["3. CSRF<br/>Token Validation"]
    CSRF --> HEADERS["4. Security Headers<br/>HSTS, CSP, X-Frame"]
    HEADERS --> APP["Application<br/>Route Handler"]

1. CORS Middleware

Property Value
Allowed origins Production domain + localhost:5173 (dev) via ALLOWED_ORIGINS env
Allow credentials true (required for HttpOnly cookie transmission)
Allowed methods All standard HTTP methods
Expose headers Limited set

2. BrowserOnly Middleware

Rejects non-browser requests in production by validating Origin and Referer headers that browsers send automatically.

Excluded from check: /webhook/*, /public/*, /setup/*, /health, /static/*, /docs, landing pages, SEO files.

3. CSRF Middleware

Extracts X-CSRF-Token header on state-changing methods (POST, PUT, DELETE, PATCH) and stores it in request.state for downstream validation against the session token.

4. Security Headers Middleware

Injects security response headers on every response (replaces NGINX security-headers.conf when running behind ALB):

Header Value Purpose
Strict-Transport-Security max-age=31536000; includeSubDomains; preload Force HTTPS for 1 year
Content-Security-Policy Restrictive policy with explicit allowlists Prevent XSS, data injection
X-Content-Type-Options nosniff Prevent MIME sniffing
X-Frame-Options SAMEORIGIN Prevent clickjacking
X-XSS-Protection 1; mode=block Legacy XSS filter
Cross-Origin-Opener-Policy same-origin Spectre mitigation
Cross-Origin-Resource-Policy same-origin Spectre mitigation
Referrer-Policy strict-origin-when-cross-origin Limit referrer leakage
Permissions-Policy geolocation=(), microphone=(), camera=(), payment=() Disable unused APIs