Authentication

Authentication flow covering JWT access/refresh tokens, OAuth2, WebAuthn passkeys, magic links, and RBAC permission checks

Auth Methods

MQUAD supports four authentication methods:

MethodEndpointSecurity
PasswordPOST /api/v1/account/loginBCrypt hashed
Magic LinkPOST /api/v1/account/login/magic-linkEmail-based token
PasskeyPOST /api/v1/account/login/passkeyWebAuthn/FIDO2
OAuth2Via backend social providersOAuth2 standard

JWT Token Structure

{
  "sub": "user-uuid",
  "tenant_id": "tenant-uuid-or-null",  # null = super admin
  "role": "admin|editor|viewer",
  "permissions": ["survey:read", "survey:write"],
  "exp": 1715673600,
  "iat": 1715587200
}

Authentication Flow

Client                    Backend                     Redis
  │                         │                          │
  │  POST /account/login    │                          │
  │────────────────────────>│                          │
  │                         │                          │
  │  ├─ Verify credentials  │                          │
  │  ├─ Generate JWT        │                          │
  │  ├─ Store refresh token─│─────────────────────────>│
  │                         │                          │
  │<────────────────────────│                          │
  │  { access_token,        │                          │
  │    refresh_token,       │                          │
  │    expires_in }         │                          │

Login Examples

Password Login

curl -X POST http://localhost:8000/api/v1/account/login \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@example.com", "password": "Test@123!"}'

Passkey Login

# WebAuthn registration
POST /api/v1/account/passkey/register
# Returns challenge for browser WebAuthn API

POST /api/v1/account/passkey/login
# Verifies authenticator assertion
POST /api/v1/account/email_magic_link_login
# Sends email with one-time link

GET /api/v1/account/email_magic_link_validate?token=<token>
# Validates token and returns JWT

RBAC Permission Checks

Admin status comes from permissions list, not a boolean field:

# WRONG — never do this
is_super_admin = token_data.is_super_admin  # DON'T

# RIGHT — always check permissions
from common.rbac_helpers import is_super_admin
is_super_admin(token_data.permissions)  # DO

TokenData Model

class TokenData(BaseModel):
    sub: str                          # User ID
    tenant_id: Optional[str] = None   # Tenant ID (null for super admin)
    permissions: List[str]            # Permission strings
    # NO is_super_admin: bool field!