Authentication
Truss provides a self-service authentication system powered by Ory Kratos. It covers login, registration, account recovery, the settings flow, and multi-factor authentication (TOTP, WebAuthn security keys, recovery codes), plus passwordless options (passkeys and magic links). Everything is available via API and client SDKs.
Admin identity management (listing, creating, banning, or impersonating users) is not part of the open-source core. You can read identities through the service-role GET /v1/auth/identities endpoint or manage them directly via the Ory Kratos Admin API. Full admin identity management is a Truss Cloud feature.
Set these environment variables in apps/api/.env:
KRATOS_PUBLIC_URL=http://localhost:4433KRATOS_ADMIN_URL=http://localhost:4434KRATOS_ADMIN_TOKEN=your-admin-tokenTRUSS_AUTH_REQUIRED=trueWith TRUSS_AUTH_REQUIRED=true, the dashboard requires login. Set to false for local development without auth.
Optional configuration:
# Social/OIDC providers (comma-separated)KRATOS_OIDC_PROVIDERS=google,github,apple,microsoft
# Identity schema ID (defaults to "default")KRATOS_IDENTITY_SCHEMA_ID=defaultLogin Methods
Section titled “Login Methods”Email + Password
Section titled “Email + Password”The standard credential-based login flow. Truss uses Kratos API flows (not browser flows) to avoid CSRF issues when the frontend and backend are on different origins.
Dashboard: Authentication > Overview (login form)
Flow:
- Frontend calls
GET /api/auth/loginto initialize a Kratos login flow - User submits credentials via
POST /api/auth/login - Server stores the session token in an HttpOnly cookie (
truss_session) - Subsequent requests are authenticated via the cookie
# 1. Initialize login flowcurl http://localhost:8787/api/auth/login
# 2. Submit credentialscurl -X POST http://localhost:8787/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]", "password": "securepassword123" }'TOTP MFA (Authenticator App)
Section titled “TOTP MFA (Authenticator App)”Time-based one-time password MFA using apps like Google Authenticator or Authy. Users can set up, verify, and remove TOTP from the settings page.
Dashboard: Authentication > Settings (MFA section)
API Endpoints:
| Method | Path | Description |
|---|---|---|
GET | /api/auth/mfa/status | Get current MFA status (TOTP enabled, WebAuthn devices, recovery codes) |
POST | /api/auth/mfa/totp/setup | Start TOTP setup — returns QR code URI and secret |
POST | /api/auth/mfa/totp/verify | Verify TOTP code to complete setup |
DELETE | /api/auth/mfa/totp | Remove TOTP from the account |
// Check MFA statusconst status = await fetch(`${TRUSS_URL}/api/auth/mfa/status`, { credentials: "include",}).then(r => r.json());// { totp: true, webauthn: false, recovery_codes: true, devices: [] }
// Start TOTP setupconst setup = await fetch(`${TRUSS_URL}/api/auth/mfa/totp/setup`, { method: "POST", credentials: "include",}).then(r => r.json());// { totpUrl: "otpauth://totp/Truss:[email protected]?secret=...", secret: "JBSWY3DPEHPK3PXP" }
// Verify TOTP codeawait fetch(`${TRUSS_URL}/api/auth/mfa/totp/verify`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ totp_code: "123456" }),});
// Remove TOTPawait fetch(`${TRUSS_URL}/api/auth/mfa/totp`, { method: "DELETE", credentials: "include",});# Check MFA statuscurl http://localhost:8787/api/auth/mfa/status \ -H "Cookie: truss_session=your-session-token"
# Start TOTP setupcurl -X POST http://localhost:8787/api/auth/mfa/totp/setup \ -H "Cookie: truss_session=your-session-token"
# Verify TOTP codecurl -X POST http://localhost:8787/api/auth/mfa/totp/verify \ -H "Cookie: truss_session=your-session-token" \ -H "Content-Type: application/json" \ -d '{"totp_code": "123456"}'
# Remove TOTPcurl -X DELETE http://localhost:8787/api/auth/mfa/totp \ -H "Cookie: truss_session=your-session-token"WebAuthn MFA (Security Keys)
Section titled “WebAuthn MFA (Security Keys)”Hardware security key support (YubiKey, etc.) via the FIDO2/WebAuthn protocol. Setup, verification, and removal are handled through the settings flow.
Dashboard: Authentication > Settings (MFA section)
API Endpoints:
| Method | Path | Description |
|---|---|---|
POST | /api/auth/mfa/webauthn/setup | Start WebAuthn registration — returns credential creation options |
POST | /api/auth/mfa/webauthn/verify | Complete WebAuthn registration with attestation response |
DELETE | /api/auth/mfa/webauthn | Remove WebAuthn credential |
// Start WebAuthn setup — returns options for navigator.credentials.create()const options = await fetch(`${TRUSS_URL}/api/auth/mfa/webauthn/setup`, { method: "POST", credentials: "include",}).then(r => r.json());
// Browser handles the key interactionconst credential = await navigator.credentials.create({ publicKey: options.publicKey,});
// Complete registrationawait fetch(`${TRUSS_URL}/api/auth/mfa/webauthn/verify`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ webauthn_register_displayname: "My YubiKey", webauthn_register: JSON.stringify(credential), }),});
// Remove WebAuthn credentialawait fetch(`${TRUSS_URL}/api/auth/mfa/webauthn`, { method: "DELETE", credentials: "include",});# Start WebAuthn setupcurl -X POST http://localhost:8787/api/auth/mfa/webauthn/setup \ -H "Cookie: truss_session=your-session-token"
# Remove WebAuthn credentialcurl -X DELETE http://localhost:8787/api/auth/mfa/webauthn \ -H "Cookie: truss_session=your-session-token"Recovery Codes
Section titled “Recovery Codes”Backup codes for account recovery when MFA devices are unavailable. Generate a set of one-time codes, confirm them, or revoke them.
Dashboard: Authentication > Settings (MFA section)
API Endpoints:
| Method | Path | Description |
|---|---|---|
POST | /api/auth/mfa/recovery-codes/generate | Generate a new set of recovery codes |
POST | /api/auth/mfa/recovery-codes/confirm | Confirm codes have been saved (activates them) |
DELETE | /api/auth/mfa/recovery-codes | Revoke all recovery codes |
# Generate recovery codescurl -X POST http://localhost:8787/api/auth/mfa/recovery-codes/generate \ -H "Cookie: truss_session=your-session-token"# Returns: { "codes": ["abc123", "def456", ...] }
# Confirm codes savedcurl -X POST http://localhost:8787/api/auth/mfa/recovery-codes/confirm \ -H "Cookie: truss_session=your-session-token"
# Revoke all codescurl -X DELETE http://localhost:8787/api/auth/mfa/recovery-codes \ -H "Cookie: truss_session=your-session-token"Passkeys
Section titled “Passkeys”Passwordless FIDO2/WebAuthn assertion flow. Users can sign in with biometrics or a security key without entering a password.
Dashboard: Authentication > Login (passkey option)
API Endpoints:
| Method | Path | Description |
|---|---|---|
GET | /api/auth/login/passkey | Initialize a passkey login flow — returns assertion options |
POST | /api/auth/login/passkey | Complete passkey login with assertion response |
// Initialize passkey loginconst options = await fetch(`${TRUSS_URL}/api/auth/login/passkey`).then(r => r.json());
// Browser handles the key interactionconst assertion = await navigator.credentials.get({ publicKey: options.publicKey,});
// Complete loginconst session = await fetch(`${TRUSS_URL}/api/auth/login/passkey`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ webauthn_login: JSON.stringify(assertion) }),}).then(r => r.json());# Initialize passkey login flowcurl http://localhost:8787/api/auth/login/passkey
# Complete passkey login (assertion response from browser)curl -X POST http://localhost:8787/api/auth/login/passkey \ -H "Content-Type: application/json" \ -d '{"webauthn_login": "<assertion-json>"}'Email OTP
Section titled “Email OTP”Passwordless login via a one-time code sent to the user’s email address. The user enters the code to complete authentication.
This is configured in Kratos as the code strategy. The flow uses the standard Kratos settings flow with method: "code".
Dashboard: Authentication > Login
Magic Link
Section titled “Magic Link”Email-based passwordless login. The user receives a link that authenticates them when clicked.
Dashboard: Authentication > Login (magic link option)
API Endpoints:
| Method | Path | Description |
|---|---|---|
POST | /api/auth/login/magic-link | Send a magic link to the user’s email |
# Send magic linkcurl -X POST http://localhost:8787/api/auth/login/magic-link \ -H "Content-Type: application/json" \The Kratos link strategy handles token generation, email delivery, and session creation when the link is clicked.
Social / OIDC Login
Section titled “Social / OIDC Login”Connect 18+ social identity providers for one-click sign-in.
Supported providers: Google, GitHub, Apple, Microsoft, Discord, GitLab, Facebook, Twitter/X, LinkedIn, Slack, Spotify, Twitch, Bitbucket, Dropbox, Yandex, VK, Dingtalk, and any custom OIDC provider.
Configuration:
# Enable providers (comma-separated)KRATOS_OIDC_PROVIDERS=google,github,apple,microsoft
# Each provider needs its own credentials in the Kratos config:# - client_id# - client_secret# - issuer_url (for generic OIDC)# - scope# - mapper_url (Jsonnet identity mapping)Once configured in Kratos, OIDC providers surface as login options in the standard Kratos login flow returned by GET /api/auth/login.
Identity Management
Section titled “Identity Management”Admin identity operations (listing, creating, updating, banning, or impersonating users) are not part of the open-source core. You have two options:
- Read-only via Truss: the service-role
GET /v1/auth/identitiesendpoint lists identities andGET /v1/auth/identities/:idreturns a single identity (see Client API below). - Full management via Kratos: create, update, delete, and manage identities directly through the Ory Kratos Admin API using
KRATOS_ADMIN_URL.
Full admin identity management (a user-management GUI, bulk import/export, impersonation, bans, session administration, and login-history analytics) is a Truss Cloud feature.
Security
Section titled “Security”Breached Password Detection (HIBP)
Section titled “Breached Password Detection (HIBP)”Truss integrates with the Have I Been Pwned (HIBP) API to check passwords against known data breaches. When enabled, users cannot set passwords that appear in breach databases.
This is configured in Kratos:
selfservice: methods: password: config: haveibeenpwned_enabled: truePassword Policy
Section titled “Password Policy”Configure minimum password length, similarity checks, and other rules. Password policy is set in the Kratos configuration file.
Kratos password policy options:
selfservice: methods: password: config: min_password_length: 8 identifier_similarity_check_enabled: true haveibeenpwned_enabled: true max_breaches: 0 # Reject any breached passwordMFA Enforcement
Section titled “MFA Enforcement”Require the highest available authentication level. When enabled, users with MFA configured must always provide their second factor.
Configured in the Kratos identity schema via aal (Authenticator Assurance Level):
aal1— Password onlyaal2— Password + second factor required
Account Enumeration Protection
Section titled “Account Enumeration Protection”Kratos natively protects against user enumeration attacks. Login and registration flows return identical responses whether an account exists or not, preventing attackers from discovering valid email addresses.
This is enabled by default in Kratos and requires no additional configuration.
Brute Force Protection
Section titled “Brute Force Protection”Flow TTL limits throttle automated login attempts. Each Kratos flow has a configurable time-to-live, and expired flows must be re-initialized.
selfservice: flows: login: lifespan: 10m # Flow expires after 10 minutes registration: lifespan: 10mAccount Recovery
Section titled “Account Recovery”Users recover their own accounts through the self-service recovery flow (powered by the Kratos link or code strategy). Recovery is a two-step flow: initialize it, then submit the email along with the returned flowId.
API Endpoints:
| Method | Path | Description |
|---|---|---|
GET | /api/auth/recovery | Initialize a recovery flow |
POST | /api/auth/recovery | Submit the recovery request (email → code/link → new password) |
# 1. Initialize a recovery flow (returns a flow with an "id")curl http://localhost:8787/api/auth/recovery
# 2. Submit the recovery requestcurl -X POST http://localhost:8787/api/auth/recovery \ -H "Content-Type: application/json" \Email delivery (recovery, verification, welcome) and the email templates are configured in Kratos via its courier settings and Jsonnet/HTML templates. See the Ory Kratos email docs for template customization.
Hooks & Events
Section titled “Hooks & Events”Session Hook
Section titled “Session Hook”Auto-login after registration. When configured, users are automatically signed in after completing the registration flow (no separate login step).
This is a Kratos after-registration hook:
selfservice: flows: registration: after: password: hooks: - hook: sessionDeveloper Experience
Section titled “Developer Experience”Prebuilt UI Components
Section titled “Prebuilt UI Components”Copy-paste authentication components for common frameworks. Available in the dashboard under Authentication > SDK tab.
import { useState } from "react";
function LoginForm({ onSuccess }) { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null);
const handleSubmit = async (e) => { e.preventDefault(); const res = await fetch("/api/auth/login", { method: "GET" }); const { flow_id } = await res.json(); const login = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ flow_id, email, password }), }); if (login.ok) onSuccess(await login.json()); else setError("Invalid credentials"); };
return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="Email" /> <input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="Password" /> {error && <p style={{ color: "red" }}>{error}</p>} <button type="submit">Sign in</button> </form> );}from flask import Flask, request, redirect, sessionimport requests
app = Flask(__name__)TRUSS_URL = "http://localhost:8787"
@app.route("/login", methods=["GET", "POST"])def login(): if request.method == "POST": email = request.form["email"] password = request.form["password"]
# Initialize flow flow = requests.get(f"{TRUSS_URL}/api/auth/login").json()
# Submit credentials res = requests.post(f"{TRUSS_URL}/api/auth/login", json={ "flow_id": flow["flow_id"], "email": email, "password": password, }) if res.ok: session["token"] = res.json()["session_token"] return redirect("/dashboard") return "Invalid credentials", 401 return '<form method="POST"><input name="email"><input name="password" type="password"><button>Login</button></form>'package main
import ( "encoding/json" "fmt" "net/http" "strings")
func loginHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { r.ParseForm() email := r.FormValue("email") password := r.FormValue("password")
// Initialize flow resp, _ := http.Get("http://localhost:8787/api/auth/login") var flow map[string]string json.NewDecoder(resp.Body).Decode(&flow)
// Submit credentials body := fmt.Sprintf(`{"flow_id":"%s","email":"%s","password":"%s"}`, flow["flow_id"], email, password) loginResp, _ := http.Post("http://localhost:8787/api/auth/login", "application/json", strings.NewReader(body))
if loginResp.StatusCode == 200 { var result map[string]string json.NewDecoder(loginResp.Body).Decode(&result) http.SetCookie(w, &http.Cookie{Name: "session", Value: result["session_token"]}) http.Redirect(w, r, "/dashboard", http.StatusFound) return } http.Error(w, "Invalid credentials", 401) }}SDK Snippets
Section titled “SDK Snippets”Complete SDK examples for all 6 core auth flows (sign up, sign in, get session, update settings, recovery, logout) in 4 languages.
Dashboard: Authentication > SDK tab
import { Configuration, FrontendApi } from "@ory/client";
const kratos = new FrontendApi(new Configuration({ basePath: "http://localhost:4433", baseOptions: { withCredentials: true },}));
// Sign upconst { data: flow } = await kratos.createBrowserRegistrationFlow();await kratos.updateRegistrationFlow({ flow: flow.id, updateRegistrationFlowBody: { method: "password", password: "securePass123", },});
// Sign inconst { data: loginFlow } = await kratos.createBrowserLoginFlow();await kratos.updateLoginFlow({ flow: loginFlow.id, updateLoginFlowBody: { method: "password", password: "securePass123", },});
// Get current sessionconst { data: session } = await kratos.toSession();console.log(session.identity.traits.email);
// Logoutconst { data: logoutFlow } = await kratos.createBrowserLogoutFlow();await kratos.updateLogoutFlow({ token: logoutFlow.logout_token });import ory_client
config = ory_client.Configuration(host="http://localhost:4433")api = ory_client.FrontendApi(ory_client.ApiClient(config))
# Sign upflow = api.create_browser_registration_flow()api.update_registration_flow( flow=flow.id, update_registration_flow_body={ "method": "password", "password": "securePass123", },)
# Sign inlogin_flow = api.create_browser_login_flow()api.update_login_flow( flow=login_flow.id, update_login_flow_body={ "method": "password", "password": "securePass123", },)
# Get current sessionsession = api.to_session()print(session.identity.traits["email"])import ory "github.com/ory/client-go"
config := ory.NewConfiguration()config.Servers = ory.ServerConfigurations{{URL: "http://localhost:4433"}}client := ory.NewAPIClient(config)
// Sign upflow, _, _ := client.FrontendAPI.CreateBrowserRegistrationFlow(ctx).Execute()_, _, _ = client.FrontendAPI.UpdateRegistrationFlow(ctx). Flow(flow.Id). UpdateRegistrationFlowBody(ory.UpdateRegistrationFlowBody{ UpdateRegistrationFlowWithPasswordMethod: &ory.UpdateRegistrationFlowWithPasswordMethod{ Method: "password", Password: "securePass123", }, }).Execute()
// Sign inloginFlow, _, _ := client.FrontendAPI.CreateBrowserLoginFlow(ctx).Execute()_, _, _ = client.FrontendAPI.UpdateLoginFlow(ctx). Flow(loginFlow.Id). UpdateLoginFlowBody(ory.UpdateLoginFlowBody{ UpdateLoginFlowWithPasswordMethod: &ory.UpdateLoginFlowWithPasswordMethod{ Method: "password", Password: "securePass123", }, }).Execute()
// Get current sessionsession, _, _ := client.FrontendAPI.ToSession(ctx).Execute()fmt.Println(session.Identity.Traits.(map[string]interface{})["email"])Audit Log
Section titled “Audit Log”Authentication actions are logged to the audit trail. Query them by action type, search term, or date range via the client API.
API Endpoint:
| Method | Path | Description |
|---|---|---|
GET | /v1/audit-logs | Query audit logs (requires service_role API key) |
# Query audit logs via client APIcurl "http://localhost:8787/v1/audit-logs?action=auth.login&limit=50" \ -H "apikey: truss_sk_your_key"Logged actions include: auth.login, auth.register, auth.logout, auth.mfa.totp.setup, auth.mfa.webauthn.setup, and more.
Client API
Section titled “Client API”The client API provides identity management for external tools and scripts, authenticated via API key rather than session cookie.
Base path: /v1/auth/
| Method | Path | Description |
|---|---|---|
GET | /v1/auth/identities | List identities (requires service_role key) |
GET | /v1/auth/identities/:id | Get identity detail (requires service_role key) |
const API_KEY = "truss_sk_your_service_role_key";
// List identitiesconst users = await fetch("http://localhost:8787/v1/auth/identities", { headers: { apikey: API_KEY },}).then(r => r.json());
// Get identity detailconst user = await fetch(`http://localhost:8787/v1/auth/identities/${userId}`, { headers: { apikey: API_KEY },}).then(r => r.json());# List identitiescurl http://localhost:8787/v1/auth/identities \ -H "apikey: truss_sk_your_key"
# Get identity detailcurl http://localhost:8787/v1/auth/identities/{id} \ -H "apikey: truss_sk_your_key"Settings Flow
Section titled “Settings Flow”Users can update their own profile, password, and MFA settings through the settings flow.
Dashboard: User menu > Settings
API Endpoints:
| Method | Path | Description |
|---|---|---|
GET | /api/auth/settings | Initialize a settings flow |
POST | /api/auth/settings | Update settings (profile, password, MFA) |
# Initialize settings flowcurl http://localhost:8787/api/auth/settings \ -H "Cookie: truss_session=your-session-token"
# Update passwordcurl -X POST http://localhost:8787/api/auth/settings \ -H "Content-Type: application/json" \ -H "Cookie: truss_session=your-session-token" \ -d '{ "method": "password", "password": "newSecurePassword123" }'
# Update profile traitscurl -X POST http://localhost:8787/api/auth/settings \ -H "Content-Type: application/json" \ -H "Cookie: truss_session=your-session-token" \ -d '{ "method": "profile", "traits": {"email": "[email protected]", "name": "Alice"} }'Registration
Section titled “Registration”API Endpoints:
| Method | Path | Description |
|---|---|---|
GET | /api/auth/register | Initialize a registration flow |
POST | /api/auth/register | Complete registration |
# Initialize registrationcurl http://localhost:8787/api/auth/register
# Register with email + passwordcurl -X POST http://localhost:8787/api/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]", "password": "securePass123" }'