Skip to content

Authentication Overview

Craft Easy ships with a complete passwordless authentication stack. Every mechanism is implemented in craft_easy.core.auth and exposed by the built-in /auth/* routes — no extra libraries to pull in, no hand-rolled token handling.

What is included

Method Use case Page
OTP (Email/SMS) Primary login flow for end users. Sends a one-time code over email or SMS. OTP
OAuth2 Social login via Google, Microsoft and GitHub. Pluggable for any custom provider. OAuth2
API keys Machine-to-machine access for services, integrations and scheduled jobs. API Keys
TOTP / 2FA Second factor with Google Authenticator, Authy or any RFC 6238 client. TOTP
WebAuthn / Passkeys Hardware-backed, phishing-resistant authentication. WebAuthn
Sessions & JWT Stateless ES512-signed tokens with blacklisting and refresh. Sessions
Abuse protection Disposable email blocking, rate limiting and CAPTCHA. Abuse Protection

All methods terminate in the same place: a signed JWT token issued by JWTService. Once a client holds a token, every downstream feature (access control, tenant isolation, audit logging) behaves identically regardless of how the user logged in.

Choosing a method

┌───────────────────────────────────────────────────────┐
│ Is the caller a human user?                           │
│   Yes → Does the tenant require strong auth?          │
│          Yes → WebAuthn / Passkeys (+ OTP fallback)   │
│          No  → OTP (Email/SMS)                        │
│   No  → Is it a partner integration?                  │
│          Yes → API key (+ signed JWT for OAuth-like)  │
│          No  → Service account with API key          │
│                                                       │
│ Also available: OAuth2 for social login               │
│                 TOTP as a second factor               │
└───────────────────────────────────────────────────────┘

A user can enroll in several factors at once — OTP is always available as the first factor, TOTP or WebAuthn can be added as an upgrade.

The AUTH_ENABLED flag

Authentication is centrally toggled by a single setting:

# settings.py
class Settings(CraftEasySettings):
    AUTH_ENABLED: bool = True
Value Behaviour
True (default) All /auth/* endpoints are active. require_auth rejects unauthenticated requests with 401.
False require_auth returns an anonymous TokenPayload (user_id="anonymous"). Intended only for local development against a non-production database.

Never ship AUTH_ENABLED=false to any environment that holds real data.

Token shape

Every successful login response follows this shape:

{
  "token": "eyJhbGciOiJFUzUxMiJ9...",
  "token_type": "user",
  "expires": "2026-04-05T12:30:00Z",
  "requires_2fa": false
}
  • token — opaque to the caller, contains the signed TokenPayload.
  • token_type — one of user, pre_auth (awaiting 2FA), or service.
  • expires — ISO-8601 UTC timestamp when the token stops being valid.
  • requires_2fa — when true, the client must complete /auth/2fa/verify before calling any other endpoint.

Include the token in every subsequent request as:

X-API-Key: eyJhbGciOiJFUzUxMiJ9...

See Sessions & Tokens for the full payload layout, refresh rules and blacklisting.

Dependencies in your routes

Pull the current user into any handler with require_auth:

from fastapi import APIRouter, Depends
from craft_easy.core.auth import require_auth, TokenPayload

router = APIRouter()

@router.get("/me")
async def me(user: TokenPayload = Depends(require_auth)):
    return {"user_id": user.user_id, "tenant_id": user.tenant_id}

Use optional_auth when an endpoint should accept both anonymous and authenticated callers:

from craft_easy.core.auth import optional_auth

@router.get("/public-with-personalization")
async def page(user: TokenPayload | None = Depends(optional_auth)):
    if user is None:
        return {"personalized": False}
    return {"personalized": True, "name": user.user_name}

Layered security

Authentication is layer 0. The subsequent layers are documented separately and all consume the same TokenPayload:

  1. Authentication (this section) — who are you?
  2. Scope guards — system/partner/tenant boundary.
  3. Feature guards — endpoint-level capabilities.
  4. Attribute access — field-level read/write.

Every page in this section links back to the concrete module under craft_easy/core/auth/, so you can always drill into the source when a configuration option is unclear.