Authentication Overview¶
Craft Easy ships with a complete authentication stack covering both human users and machine-to-machine (M2M) callers. 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.
Authentication types¶
Craft Easy supports two distinct authentication tracks:
-
User authentication — humans logging in via OTP, OAuth2 (Google/Microsoft), BankID, TOTP/2FA, or WebAuthn/Passkeys. Sessions are tracked server-side and tokens issued via
Authorization: Bearer <session_token>. -
Machine-to-machine (M2M) authentication — services calling Craft Easy APIs via OAuth 2.0 Client Credentials with
private_key_jwtassertion (RFC 7523), optionally bound to the client key with DPoP (RFC 9449). ApiClients have capability lists that map to the same Capability Registry as human roles.
Choose the track that matches the caller type.
User auth¶
| 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 |
| 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 |
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.
M2M auth¶
| Component | Use case | Page |
|---|---|---|
| Overview | Architecture, flow diagram, and getting started guide. | M2M Overview |
| API Clients | Register services, manage keys, assign capabilities. | API Clients |
| Private Key JWT | OAuth 2.0 Client Credentials with private_key_jwt assertion (RFC 7523). |
Private Key JWT |
| DPoP | Optional token binding to prevent stolen-token abuse (RFC 9449). | DPoP Token Binding |
| Audit | Event logging, Prometheus metrics, anomaly detection. | Audit & Monitoring |
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 → Register an ApiClient with the M2M flow: │
│ POST /api-clients → upload key → POST │
│ /auth/token with private_key_jwt assertion │
│ │
│ Also available: OAuth2 for social login │
│ TOTP as a second factor │
│ DPoP for sender-constrained M2M tokens│
└───────────────────────────────────────────────────────┘
Abuse protection¶
Rate limiting, disposable email blocking, and CAPTCHA apply to both tracks. See Abuse Protection for the full configuration.
The AUTH_ENABLED flag¶
Authentication is centrally toggled by a single setting:
| 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¶
User tokens¶
Every successful user 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 signedTokenPayload.token_type— one ofuser,pre_auth(awaiting 2FA).expires— ISO-8601 UTC timestamp when the token stops being valid.requires_2fa— whentrue, the client must complete/auth/2fa/verifybefore calling any other endpoint.
M2M tokens¶
The M2M token endpoint returns a standard OAuth 2.0 response:
{
"access_token": "eyJhbGciOiJFUzUxMiJ9...",
"token_type": "Bearer",
"expires_in": 1800,
"scope": "payments:read settlements:create"
}
Both token types are used as:
See Sessions & Tokens for the full user token payload layout, refresh rules and blacklisting. See Private Key JWT for M2M token details.
Dependencies in your routes¶
Pull the current user or service identity 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:
- Authentication (this section) — who are you?
- Capabilities — endpoint-level permissions, auto-generated from resources.
- Roles & Assignments — named capability sets assigned to users within scopes.
- Sensitive Fields — field-level read/write control.
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.