# Autentico OAuth 2.0 / OIDC Identity Provider [![Go Version](https://img.shields.io/badge/go-1.23+-blue.svg)](https://golang.org/dl/) [![License](https://img.shields.io/badge/license-AGPL--v3-blue.svg)](LICENSE) **Auténtico is a self-contained OAuth 2.0 / OpenID Connect (OIDC) Identity Provider built with Go. It handles the full authentication lifecycle — login, MFA, passkeys, sessions, token issuance, and admin — in a single binary backed by SQLite. No external database, no infrastructure dependencies, no ceremony.**

Auténtico first-run onboarding and admin UI

From an empty directory to a running identity provider.
Onboarding, self-signup, registering a client, 2FA enrollment, and the audit log.
Same walkthrough as an MP4 →

Identity infrastructure is typically complex to operate: a separate database to provision and back up, a cache tier, a worker queue, multiple services to keep running, and credentials to rotate. Auténtico takes a different approach. The entire IdP — authentication, token issuance, session management, and the admin UI — runs as one Go binary backed by a single SQLite file. You deploy one thing and it works. If you have multiple projects, point them all at one instance and centralize your users, sessions, and security in one place. Auténtico implements OAuth2 and OpenID Connect correctly. It is not a simplified or non-standard subset. Authorization Code + PKCE, refresh tokens, token introspection, OIDC discovery, RS256-signed JWTs, WebAuthn/passkeys, TOTP, and email OTP are all standard-compliant. The simplicity is operational, not protocol-level. **Correctness is verified through 1,800+ tests, RFC-by-RFC compliance audits, official OIDC conformance tests, and full-flow load testing.** --- ## Documentation Access the full documentation at [getautentico.dev](https://getautentico.dev) ## Live Demo Try Autentico instantly: [Launch a Live Demo](https://demo.getautentico.dev/launch) _Each demo session provisions a dedicated, ephemeral Autentico instance—isolated for your use, with all data and configuration automatically purged after 24 hours. No shared state, no persistence, no surprises._ ## Using Autentico? We'd love to hear from you — what you're protecting, what's working, what's missing. [Share your use case in Discussions](https://github.com/eugenioenko/autentico/discussions/113) --- ## Why Auténtico? Most production identity systems require you to operate multiple services before users can log in. A typical self-hosted setup involves a database server, a cache, a queue, and the identity service itself — each with its own configuration, backup requirements, and failure modes. Auténtico removes that stack: - **Single binary** — one executable, no sidecars, no process manager complexity - **Embedded SQLite** — no separate database server; the entire state lives in one file - **No external infrastructure** — no Redis, no Postgres, no message queues - **Built-in admin UI** — a React dashboard compiled into the binary; nothing to deploy separately - **Standards-compliant OAuth 2.0 / OIDC** — relying parties configure themselves automatically via OIDC Discovery; tokens are RS256-signed JWTs verifiable without calling home The operational surface area is a binary and a `.env` file. That is a meaningful reduction in the things that can break. The tradeoff is scale ceiling: SQLite serializes writes, and a single binary can't span multiple hosts. For most teams running internal tools, small-to-mid-sized applications, or self-hosted services, these constraints never become relevant. When they do, they're the right kind of problem to have. --- ## Who Auténtico Is For Auténtico is a good fit for: - **Small teams and startups** that need OAuth2/OIDC for their product but don't want to operate a full identity platform - **Internal tools and self-hosted applications** where simplicity and low maintenance overhead matter more than enterprise-scale features - **Developers evaluating OIDC** who want a running, real implementation to work against rather than a mock - **Side projects and indie developers** who need user authentication without depending on a third-party service or running a heavy stack - **CI and test suites** that need a real OIDC provider as a test fixture — Autentico starts in ~375ms, making fresh-server-per-test practical without Docker or mocks It is not designed for organizations that require horizontal scaling of the auth tier, active-active multi-region deployments, or enterprise compliance features (SCIM, LDAP federation, custom authorization policies). Those requirements point to a different class of identity platform. --- ## Table of Contents - [Why Auténtico?](#why-auténtico) - [Who Auténtico Is For](#who-auténtico-is-for) - [Features](#features) - [Tech Stack](#tech-stack) - [Architecture Overview](#architecture-overview) - [Getting Started](#getting-started) - [Configuration](#configuration) - [Bootstrap Settings (.env)](#bootstrap-settings-env) - [Global Settings (Runtime)](#global-settings-runtime) - [Per-Client Overrides](#per-client-overrides) - [Authentication Modes](#authentication-modes) - [Multi-Factor Authentication](#multi-factor-authentication) - [Trusted Devices](#trusted-devices) - [Admin UI](#admin-ui) - [Account UI](#account-ui) - [Endpoints](#endpoints) - [Supported Grant Types](#supported-grant-types) - [Client Interaction Examples](#client-interaction-examples) - [Security Considerations](#security-considerations) - [Deployment & Operations](#deployment--operations) - [Kubernetes (Helm)](#kubernetes-helm) - [Testing](#testing) - [Use as a Test Fixture](#use-as-a-test-fixture) - [API Documentation](#api-documentation) - [Contributing](#contributing) - [License](#license) --- ## Features ### Standards & Protocol - **OIDC Discovery** — publishes `/.well-known/openid-configuration` so relying parties auto-configure without hardcoding endpoints - **JWK Set** — exposes public signing keys at `/oauth2/.well-known/jwks.json` for independent token verification - **RS256 JWT Signing** — asymmetric signing; the private key never leaves the IdP - **Authorization Code Flow + PKCE** (RFC 7636, S256 enforced by default) — the right default for web and native apps; federation validates registered clients, redirect URIs, scopes, and PKCE before issuing codes - **Authorization request validation** — signed continuation parameters are checked against the current client, callback URI, scopes, and PKCE before signup, email verification, magic-link login, and passkey authentication. - **Code-only authorization responses** — authorization and client registration support only `response_type=code`; implicit and hybrid response types are rejected. - **Federation claim requirements** — optional per-provider string/boolean equality and Go regex rules, enforced on verified ID tokens before account linking or login. Admin rule builder and read-only draft tester share the login matcher. - **Federation login CSRF protection** — short-lived browser bindings and atomic single-use state survive restarts; independent upstream nonces prevent response injection. - **Authorization response modes** — discovery advertises query encoding; unsupported modes are rejected before login or SSO. - **Authorization request objects** — discovery explicitly disables `request_uri`; unsupported request objects are rejected before login or SSO. - **PKCE validation** — authorization rejects unsupported methods and methods without a challenge before login or SSO code issuance; all browser flows share the same PKCE policy. An omitted method means `plain`, rejected by default S256 enforcement. - **Token grant errors** — unsupported grants return `unsupported_grant_type`; missing grants remain `invalid_request`, and client grant restrictions remain `unauthorized_client`. - **OAuth client authentication** — confidential device clients authenticate at issuance and polling. Protocol endpoints accept one Basic or body credential set, decode OAuth Basic correctly, reject URI/ambiguous credentials, and return matching authentication challenges. - **Atomic grant redemption** — authorization codes, device codes and refresh tokens have one winning exchange. Credential consumption and token/session storage commit together; failed storage leaves the credential available for retry. - **Refresh client authentication** — every refresh identifies the issuing client; confidential clients authenticate with their secret. Inactive clients, disallowed refresh grants, mismatched clients and unbound historical tokens are rejected. - **Resource Owner Password Credentials** — supported for legacy client compatibility (see [ROPC note](#on-ropc-support)); required email verification also applies to non-admin password grants - **Refresh Token Grant** — long-lived sessions without re-authentication - **Token Introspection** (RFC 7662) — for resource servers to validate opaque tokens - **Token Revocation** (RFC 7009) — explicit token invalidation; privileged bearer revocation requires the admin audience and current admin role - **UserInfo Endpoint** — standard OIDC identity claims - **Keycloak-compatible paths** — `/oauth2/protocol/openid-connect/token` and `/oauth2/protocol/openid-connect/userinfo` are registered alongside standard paths, easing migration from Keycloak ### Authentication - **Three auth modes** — `password`, `password_and_passkey`, or `passkey_only` — switchable at runtime without restart - **Passkeys (WebAuthn)** — hardware-backed FIDO2 authentication via platform authenticators and security keys; passkey-only mode supports signup without passwords - **Passkey signup policy** — self-signup and client overrides apply at ceremony start and completion. Required email verification precedes sessions and authorization codes; registration is rate-limited and bound to the initiating browser. - **TOTP MFA** — time-based one-time passwords with in-browser QR code enrollment (no out-of-band enrollment link required) - **TOTP attempt budget** — five failed verifications lock enrolled TOTP across browser login, password grants and removal. Fresh challenges and successful password checks do not reset the budget. - **Single-use recovery and MFA** — concurrent requests can redeem a password reset link or browser MFA challenge only once. Password reset commits the new password and OAuth/SSO session invalidation together. - **Email OTP MFA** — one-time codes delivered via SMTP - **Trusted Devices** — after MFA, users can mark a device as trusted for a configurable period, skipping MFA on return visits - **Account Lockout** — configurable failed-attempt threshold and lockout duration, with admin unlock capability - **SSO Sessions (IdP Sessions)** — persistent sessions across browser restarts with configurable idle timeout; returning users skip the login page entirely ### User & Client Management - **Dynamic client registration** — register and manage OAuth2 clients (confidential and public) via REST API or Admin UI. Authentication method `none` infers a public client when type is omitted; contradictory authentication metadata is rejected. Existing confidential clients keep their secrets. - **Per-client configuration overrides** — token TTLs, allowed audiences, self-signup, session idle timeout, and trusted device settings can be tuned per client without touching global defaults - **Self-signup** — optionally allow end users to register accounts on the login page, globally or per client - **User CRUD** — full user lifecycle management with role support (`user`, `admin`) - **Custom claims** — assign custom string claims to users and groups; group claims are inherited as `group.claim`, or optionally merged into arrays under their local names, with individual claims taking precedence; emitted into the ID token, access token, and UserInfo response when the `custom_claims` scope is granted - **User groups** — organize users into groups; group names are emitted as a `groups` claim when the `groups` scope is granted - **Soft deletes** — users and clients are deactivated, not destroyed, preserving audit history - **Account self-service** — users can manage their own profile, security settings, sessions, passkeys, MFA, and connected providers via the built-in Account UI ### Operations - **Built-in Admin UI** — a React dashboard embedded into the binary at build time; zero separate deployments - **Runtime settings** — most configuration lives in the database and can be updated via the Admin UI without restarting the server - **Background cleanup** — a configurable background goroutine purges expired tokens, sessions, auth codes, MFA challenges, and passkey challenges; the retention window is tunable, and OAuth token/session rows survive through both token lifetimes for refresh and replay detection - **Graceful shutdown** — SIGTERM handling drains in-flight requests before exit - **Guided onboarding** — first-run setup flow that bootstraps the admin account; the server surfaces the onboarding URL at startup until complete - **Docker-ready** — multi-stage Dockerfile produces a slim Debian image with a single binary; put your own reverse proxy in front for TLS --- ## Tech Stack | Layer | Choice | Rationale | | ----------- | ----------------------------- | ------------------------------------------------------------- | | Language | Go 1.25 | Single-binary deployment, strong concurrency, type safety | | Database | SQLite (`modernc.org/sqlite`) | No CGo, no external server, sufficient for IdP write patterns | | JWT Signing | RS256 (RSA 2048) | Asymmetric — relying parties verify without holding secrets | | WebAuthn | `go-webauthn/webauthn` | Standards-compliant FIDO2/WebAuthn implementation | | TOTP | RFC 6238 compliant | Compatible with Google Authenticator, Authy, 1Password, etc. | | CSRF | `gorilla/csrf` | Protects browser-facing form endpoints | | Admin UI | React + yarcl (Vite) | Embedded into the binary via `go:embed` | | Testing | Testify + in-memory SQLite | Isolated, fast unit and integration tests | | Docs | Swagger/OpenAPI | Interactive API reference | --- ## Architecture Overview ### Package Structure Each package in `pkg/` owns a vertical slice of IdP functionality. The convention is consistent: `model.go` for types, `handler.go` for HTTP, `create/read/update/delete.go` for database operations, `service.go` for business logic. | Package | Role | | ------------------- | -------------------------------------------------------------------------------- | | `pkg/authorize` | Authorization endpoint — renders login page, validates client and redirect URI | | `pkg/login` | Login form submission, credential validation, MFA challenge creation | | `pkg/mfa` | MFA challenge verification, TOTP enrollment, email OTP delivery | | `pkg/passkey` | WebAuthn registration and authentication ceremony handlers | | `pkg/trusteddevice` | Trusted device token issuance and cookie management | | `pkg/token` | Token endpoint — authorization code exchange, refresh, revocation | | `pkg/session` | OAuth2 session lifecycle and admin session management | | `pkg/idpsession` | IdP-level SSO sessions — cross-request browser sessions | | `pkg/client` | OAuth2 client registration, CRUD, and authentication | | `pkg/user` | User identity management — CRUD, authentication, lockout | | `pkg/groupclaim` | Group custom claims — admin CRUD, inherited through membership | | `pkg/userclaim` | Custom per-user claims — CRUD and token/UserInfo embedding | | `pkg/signup` | Self-service user registration flow | | `pkg/onboarding` | First-run admin account creation | | `pkg/appsettings` | Runtime settings — DB persistence, loading into config | | `pkg/introspect` | Token introspection endpoint | | `pkg/userinfo` | UserInfo endpoint | | `pkg/wellknown` | OIDC discovery document and JWKS | | `pkg/middleware` | CORS, CSRF, logging, admin auth, audience validation | | `pkg/cleanup` | Background goroutine for expired record purging | | `pkg/account` | Account self-service API and embedded React Account UI (from `pkg/account/dist`) | | `pkg/admin` | Embedded Admin UI (static files + stats handler) | | `pkg/config` | Bootstrap and runtime configuration | | `pkg/db` | SQLite initialization, schema, and incremental migrations | | `pkg/key` | RSA key loading (env or ephemeral) and JWK Set generation | ### Architecture Philosophy Auténtico treats operational simplicity as a first-class design goal. Each architectural decision is evaluated against the question: does this reduce or increase the operational burden on the person running this? Eliminating the external database eliminates an entire category of failure modes. There is no connection pool to tune, no managed service to provision, no separate credential rotation for the database layer, and no network partition between the IdP and its storage. The result is an IdP you can run anywhere a Go binary runs — a VM, a container, a Raspberry Pi, a developer laptop. The codebase is deliberately un-clever. Each package does one thing. There are no frameworks beyond the standard library, no abstraction layers that obscure what a request actually does. New contributors and future maintainers should be able to read any handler and understand the complete request lifecycle without jumping through multiple layers of indirection. ### Why SQLite SQLite is an intentional architectural choice. For identity workloads at this scale, the write serialization limit (~500 sustained writes/sec) represents approximately 50,000 logins/hour — more than most deployments will ever approach. In exchange, you get zero operational overhead: no connection pool, no network partition, no separate credential management. When load characteristics genuinely justify migration, the `pkg/db` boundary makes that tractable. Until then, the operational simplicity is worth considerably more than theoretical headroom. **Observed performance** — full PKCE auth code flow (authorize → login → token → introspect → refresh), 60s sustained load, 8 cores, single process, SQLite WAL mode with read/write connection pool: | Concurrency | Error rate | Login p95 | Token p95 | Assessment | |-------------|-----------|-----------|-----------|------------| | 20 VUs | 0% | 248ms | 300ms | Comfortable — imperceptible to users | | 100 VUs | 0% | 1.19s | 1.56s | Sustained load — fully functional | | 200 VUs | 0% | 2.37s | 2.94s | Moderate pressure — noticeable but acceptable | | 500 VUs | 0% | 5.76s | 7.34s | Stress ceiling — degraded experience | In practice, "200 concurrent logins" corresponds to **20,000–40,000 daily active users** under a typical enterprise login distribution (morning peak, sessions lasting hours). The failure mode at high concurrency is graceful queuing — no errors, just latency — because SQLite's busy timeout absorbs write contention rather than returning errors. See [`stress/README.md`](stress/README.md) for the full methodology and how to reproduce these numbers. ### Why RS256 over HS256 Symmetric signing (HS256) requires every party that verifies a token to also hold the signing secret. As relying parties multiply, so does the secret distribution problem. RS256 keeps the private key exclusively with Auténtico; relying parties verify independently using the public JWKS. This is the correct architecture for an IdP regardless of scale. ### On ROPC Support The Resource Owner Password Credentials grant is deprecated in OAuth 2.1. It is supported here deliberately to maintain backward compatibility with legacy clients and tooling that teams may already depend on. If you are building something new, use the Authorization Code flow. ROPC support can be restricted to specific clients by not granting them the `password` grant type during registration. --- ## Getting Started The full setup requires three commands: initialize configuration, start the server, and complete onboarding in the browser. No database to provision, no external services to configure. ### Prerequisites - Go 1.25 or later - `make` (optional, for Makefile targets) - Node.js 20+ and `pnpm` (only if building the Admin UI from source) ### Quick Start (pre-built binary) Download the latest binary from [GitHub Releases](https://github.com/eugenioenko/autentico/releases), then: ```bash # Generate .env with RSA key, TOTP encryption key, CSRF secret, and token secrets ./autentico init # Create the first admin account (or use the browser at /onboard after starting) ./autentico onboard --username admin --password yourpassword --email admin@example.com # Start the server ./autentico start ``` To use configuration from another location, run `./autentico start --env-file /path/to/.env`. Pass the same `--env-file /path/to/.env` to `migrate` and `onboard` to use that configuration and database. Environment variables override file values. With `--auto-setup`, a missing file is created at that path; its parent directory must exist. Relative database paths still resolve from the current working directory. The `onboard` command creates the admin account headlessly — useful for CI/CD, Docker, and automated deployments. Credentials can also be passed via environment variables (`AUTENTICO_ADMIN_USERNAME`, `AUTENTICO_ADMIN_PASSWORD`, `AUTENTICO_ADMIN_EMAIL`). Alternatively, start the server first and visit `http://localhost:9999/onboard/` to complete setup in the browser. To fetch admin-API tokens from scripts (integration tests, CI/CD), pass `--enable-admin-password-grant` on `autentico onboard` (or set `AUTENTICO_ENABLE_ADMIN_PASSWORD_GRANT=true`). This seeds the `autentico-admin` client with the `password` (ROPC) grant so `POST /oauth2/token` with `grant_type=password` can issue admin tokens headlessly. If you onboard via the browser wizard instead, add the `password` grant afterwards from the Admin UI (Clients → `autentico-admin`). MFA and account lockout still apply. ### Building from Source **1. Clone the repository** ```bash git clone https://github.com/eugenioenko/autentico.git cd autentico ``` **2. Build** ```bash # Build Admin UI + Go binary (requires pnpm) make build # Build Go binary only (reuses UI assets from a previous make build) make build-go ``` **3. Initialize configuration** ```bash ./autentico init # Or with a custom URL: ./autentico init --url https://auth.example.com ``` This generates a `.env` file with a fresh RSA private key, TOTP encryption key, CSRF secret, and token signing secrets. The RSA key is embedded as a base64-encoded PEM; the separate TOTP key is base64-encoded random 32-byte material. Re-running `init` on an existing `.env` adds only a missing TOTP key and preserves other settings and secrets. **4. Start the server** ```bash ./autentico start ``` The server starts on port 9999 and prints the key URLs (server, admin, well-known, authorize, token) to stdout. **5. Complete onboarding** If this is a fresh database, the startup output shows an **ONBOARDING** URL (e.g., `http://localhost:9999/onboard/`). Open it and fill in your administrator credentials. The URL is shown on every startup until onboarding is complete. ### Local development sample data Run `make fresh` to reset the local database and `.env`, rebuild, and start the server with HTTP development cookies and rate limiting disabled. In another terminal: ```bash make seed ``` The Node.js script uses the normal APIs to create an admin plus 100 users with realistic names and profiles, 12 groups with memberships and claims, 24 OAuth clients, 6 disabled federation providers, 12 protected apps, 15 CORS origins, 8 pending deletion requests, real sessions and tokens (including revoked ones), consent grants, 8 TOTP enrollments, and audit history. Protected apps also create their associated OAuth clients. Sample URLs and emails use `.test` domains. Sign in at `http://localhost:9999/admin/` with **admin / Password123!**. Sample user **alex.anderson** and the other generated users share that password; eight accounts also have TOTP enabled for UI testing. Repeat runs reuse named records and sample sessions, while the seeder's admin sign-in and API updates append normal audit history. Use `make seed BASE_URL=http://localhost:19999` for another local port (the server's application URL must match). The script accepts loopback origins only. `make test-seed` runs unit checks and seeds an isolated real server twice to check the data and repeat-run behavior. No extra Node.js packages are needed. --- ## Configuration Auténtico uses a three-layer configuration system, ordered from most to least immutable: ``` .env (bootstrap) → settings table (runtime) → clients table (per-client) immutable hot-reloadable per-audience ``` ### Bootstrap Settings (.env) These are infrastructure-level settings read once at startup. Changing them requires a server restart. Generated by `autentico init`. | Variable | Description | Default | | -------------------------------- | -------------------------------------------------------------- | ----------------------- | | `AUTENTICO_APP_URL` | Base URL (used to derive issuer, domain, port) | `http://localhost:9999` | | `AUTENTICO_APP_OAUTH_PATH` | Path prefix for OAuth2 endpoints | `/oauth2` | | `AUTENTICO_DB_FILE_PATH` | SQLite database file path | `./autentico.db` | | `AUTENTICO_PRIVATE_KEY` | Base64-encoded RSA private key PEM | _(generated by init)_ | | `AUTENTICO_TOTP_ENCRYPTION_KEY` | Required persistent base64-encoded 32-byte TOTP encryption key; generated by init | _(generated by init)_ | | `AUTENTICO_ACCESS_TOKEN_SECRET` | HMAC secret for access token signing | _(generated by init)_ | | `AUTENTICO_REFRESH_TOKEN_SECRET` | HMAC secret for refresh token signing | _(generated by init)_ | | `AUTENTICO_CSRF_SECRET_KEY` | Secret for CSRF token generation | _(generated by init)_ | | `AUTENTICO_CSRF_COOKIE_NAME` | CSRF cookie name | `_gorilla_csrf` | | `AUTENTICO_TRUSTED_DEVICE_COOKIE_NAME` | Trusted-device cookie name | `autentico_trusted_device` | | `AUTENTICO_FEDERATION_COOKIE_NAME` | Federation browser-cookie base name (automatic secure `__Host-` prefix) | `autentico_federation` | | `AUTENTICO_PASSKEY_REGISTRATION_COOKIE_NAME` | Passkey-registration cookie base name (automatic secure `__Host-` prefix) | `autentico_passkey_registration` | | `AUTENTICO_FORWARD_AUTH_COOKIE_PREFIX` | App-session/flow cookie prefix (automatic `__Host-` for HTTPS apps) | `autentico_fa_` | | `AUTENTICO_CSRF_SECURE_COOKIE` | Require Secure flag on CSRF cookie | `true` | | `AUTENTICO_REFRESH_TOKEN_COOKIE_ONLY` | **Opt-in security enhancement.** Delivers the refresh token as an `HttpOnly` cookie instead of in the JSON response body, preventing JavaScript (including XSS) from reading it. Non-standard — only enable if your client reads the refresh token from a cookie. | `false` | | `AUTENTICO_IDP_SESSION_SECURE` | Require Secure flag on IdP session cookie | `true` | | `AUTENTICO_JWK_CERT_KEY_ID` | Key ID (`kid`) in the JWK Set | `autentico-key-1` | | `AUTENTICO_TRUSTED_PROXIES` | Comma-separated proxy CIDRs allowed to supply client IPs via X-Forwarded-For; empty uses network peer | _(empty)_ | | `AUTENTICO_RATE_LIMIT_RPS` | Sustained requests/sec per IP on auth endpoints (0 = disabled) | `5` | | `AUTENTICO_RATE_LIMIT_BURST` | Burst size for the per-second limiter | `10` | | `AUTENTICO_RATE_LIMIT_RPM` | Sustained requests/min per IP (long-term cap) | `20` | | `AUTENTICO_RATE_LIMIT_RPM_BURST` | Burst size for the per-minute limiter | `20` | | `AUTENTICO_ANTI_TIMING_MIN_MS` | Minimum random delay (ms) on auth responses to prevent timing-based user enumeration (0 + 0 = disabled) | `50` | | `AUTENTICO_ANTI_TIMING_MAX_MS` | Maximum random delay (ms); must be > min, otherwise disabled | `150` | > In production, set all `*_SECURE` flags to `true` once you have TLS. ### Global Settings (Runtime) These live in the `settings` database table and are loaded into memory at startup. They can be updated via the Admin UI or `PUT /admin/api/settings` without restarting the server — changes take effect on the next request. | Setting Key | Description | Default | | -------------------------------- | ------------------------------------------------------ | ---------------- | | `access_token_expiration` | Access token lifetime | `15m` | | `refresh_token_expiration` | Refresh token lifetime | `720h` (30 days) | | `authorization_code_expiration` | Auth code lifetime | `10m` | | `access_token_audience` | JWT `aud` claim (JSON array) | `[]` | | `auth_mode` | `password` \| `password_and_passkey` \| `passkey_only` | `password` | | `mfa_enabled` | Require MFA for all users | `false` | | `mfa_method` | `totp` \| `email` | `totp` | | `trust_device_enabled` | Allow users to bypass MFA on trusted devices | `false` | | `trust_device_expiration` | Trusted device token lifetime | `720h` (30 days) | | `sso_session_idle_timeout` | IdP session idle timeout (`0` = disabled); resets on login and token refresh | `4h` | | `sso_session_max_age` | Absolute max session lifetime (`0` = disabled) | `720h` (30 days) | | `allow_self_signup` | Allow end users to register their own accounts | `false` | | `account_lockout_max_attempts` | Failed logins before account lock | `5` | | `account_lockout_duration` | How long the account stays locked | `15m` | | `passkey_rp_name` | WebAuthn relying party name shown to users | `Autentico` | | `cleanup_interval` | How often expired records are purged | `6h` | | `cleanup_retention` | Minimum age for a record to be eligible for cleanup | `24h` | | `validation_min_username_length` | Minimum username length | `4` | | `validation_max_username_length` | Maximum username length | `64` | | `validation_min_password_length` | Minimum password length | `6` | | `validation_max_password_length` | Maximum password length | `64` | | `validation_username_is_email` | Require username to be a valid email format | `false` | | `validation_email_required` | Require email field at registration | `false` | | `smtp_host` | SMTP server hostname | _(empty)_ | | `smtp_port` | SMTP server port | `587` | | `smtp_username` | SMTP authentication username | _(empty)_ | | `smtp_password` | SMTP authentication password | _(empty)_ | | `smtp_from` | From address for outbound email | _(empty)_ | | `theme_title` | Page title shown on login/MFA pages | `Autentico` | | `theme_logo_url` | URL to a logo image displayed on login page | _(empty)_ | | `theme_css_inline` | Inline CSS injected into login page `