Skip to content

eugenioenko/autentico

Repository files navigation

Autentico — OIDC Identity Provider

Go Report Card Test Coverage Tests OIDC Certified Go Version License

Auténtico is a self-contained 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.

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.

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.


Documentation

Access the full documentation at autentico.top

Live Demo

Try Autentico instantly: Launch a Live Demo

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.


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 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

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


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 /.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
  • Resource Owner Password Credentials — supported for legacy client compatibility (see ROPC note)
  • 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
  • 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 modespassword, 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 first-login registration in one flow
  • TOTP MFA — time-based one-time passwords with in-browser QR code enrollment (no out-of-band enrollment link required)
  • 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
  • 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)
  • 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/Ant Design 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
  • 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 minimal Alpine image with nginx for TLS termination

Tech Stack

Layer Choice Rationale
Language Go 1.23 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 + Ant Design (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/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), measured with k6 on a developer laptop, single process, SQLite backend:

Concurrency Error rate Login p95 Token p95 Assessment
20 VUs 0% 86ms 54ms Comfortable — imperceptible to users
100 VUs 0% 611ms 647ms Supported — fully functional
500 VUs 0% 3.36s 3.89s Degraded — users feel the wait

In practice, "100 concurrent logins" corresponds to 10,000–20,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.

Other operations (token refresh, introspection, OIDC discovery) are not bottlenecked by bcrypt and remain sub-10ms well beyond these concurrency levels.

See 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.21 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, then:

# Generate .env with RSA key, CSRF secret, and token secrets
./autentico init

# Start the server
./autentico start

Open http://localhost:9999/onboard/ and complete the setup form to create the first administrator account. The server is ready.

Building from Source

1. Clone the repository

git clone https://github.com/eugenioenko/autentico.git
cd autentico

2. Build

# Build Admin UI + Go binary (requires pnpm)
make build

# Build Go binary only (uses the pre-built Admin UI in pkg/admin/dist)
make build-go

3. Initialize configuration

./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, CSRF secret, and token signing secrets. The key is embedded as a base64-encoded PEM — no separate key file to manage.

4. Start the server

./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.


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_APP_ENABLE_CORS Enable CORS middleware true
AUTENTICO_DB_FILE_PATH SQLite database file path ./db/autentico.db
AUTENTICO_PRIVATE_KEY Base64-encoded RSA private key PEM (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_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_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

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) 0
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 <style> (empty)
theme_css_file Path to a CSS file loaded at runtime (empty)

Per-Client Overrides

Each registered client can override a subset of global settings. Unset fields fall through to the global value — there is no need to repeat the default. Overrides are managed via the client registration API or Admin UI.

Field Description
access_token_expiration Token lifetime for this client's tokens
refresh_token_expiration Refresh token lifetime for this client
authorization_code_expiration Auth code TTL
allowed_audiences Additional aud values in tokens for this client
allow_self_signup Override self-signup for this client's login page
sso_session_idle_timeout Override idle timeout for sessions originating from this client
trust_device_enabled Enable or disable trusted devices for this client
trust_device_expiration Trust duration for this client's users

Authentication Modes

Auténtico's auth_mode setting controls which credential types are accepted. It can be changed at runtime.

Mode Behavior
password Standard username + password. MFA is applied on top if mfa_enabled is true.
password_and_passkey Users can authenticate with either password or a registered passkey. Password flow optionally includes MFA.
passkey_only Password authentication is disabled. Users authenticate exclusively with passkeys. First-time users are guided through passkey registration during their first login attempt.

Multi-Factor Authentication

When mfa_enabled is true, all users must complete an MFA step after password authentication.

TOTP

  • Users who have not yet enrolled are shown a QR code on their next login
  • The QR code can be scanned with any TOTP app (Google Authenticator, Authy, 1Password, Bitwarden, etc.)
  • Once enrolled, TOTP is required on every subsequent login (unless on a trusted device)
  • The secret is stored per-user in the database; enrollment happens in-browser without any email or out-of-band step

Email OTP

  • A one-time code is generated and emailed to the user on each login
  • Requires SMTP settings (smtp_host, smtp_port, smtp_username, smtp_password, smtp_from) to be configured
  • No enrollment step — the code is sent immediately

Trusted Devices

When trust_device_enabled is true, the MFA page shows a "Trust this device" checkbox. If checked, a cryptographic token is stored in a long-lived cookie on the user's browser. On subsequent logins from the same device, MFA is skipped for the duration of trust_device_expiration.

Trusted device tokens are stored in the trusted_devices table and cleaned up automatically by the background cleanup process.


Login Page Theming

The login, MFA, and signup pages are server-rendered HTML. The visual appearance is controlled via CSS variables injected from the theme_css_inline or theme_css_file settings.

Available CSS variables:

:root {
  /* Typography */
  --font-size: 16px;
  --font-family:
    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    sans-serif;

  /* Colors — Light Mode */
  --color-primary: #2e5bff;
  --color-accent: #188060;
  --color-danger: #ff4848;
  --color-text: #0f0f0f;
  --color-inverse: #ffffff;
  --color-background: #f1f1f1;
  --color-card: #ffffff;
  --color-border: #696969;

  /* Layout */
  --border-radius: 2px;
  --form-width: 380px;
  --form-padding: 30px;
  --form-gap: 16px;
  --form-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);

  /* Logo */
  --logo-size: 96px;
  --logo-margin: 16px 0 32px 0;
  --logo-padding: 16px;

  /* Elements */
  --input-padding: 10px;
  --button-padding: 10px;
  --button-margin-top: 16px;
  --h1-font-size: 2rem;
  --label-padding-bottom: 4px;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --color-text: #e7e7e7;
    --color-inverse: #e7e7e7;
    --color-background: #0f0f0f;
    --color-card: #1e1f22;
    --color-border: #cccccc;
    --color-danger: #d60b0b;
  }
}

You can also set theme_logo_url to display a logo above the login form, and theme_title to customize the page title and TOTP issuer name.


Admin UI

The Admin UI is a React application (built with Ant Design) embedded into the Go binary via go:embed. It is served at /admin/ and authenticates through Auténtico itself using the auto-seeded autentico-admin OIDC client.

Pages

Dashboard

  • Live stats: total users, active clients, active sessions, recent logins (last 24h)
  • Quick action buttons: Create User, Create Client

Users

  • Full user list with username, email, role, status (active, locked, failed attempts), and creation date
  • Create users with role selection
  • Edit user details and role
  • Deactivate users (soft delete)
  • Unlock accounts that have been locked by the account lockout policy

Clients

  • Full client list with name, client ID, type (confidential/public), grant types, and status
  • Create clients with all registration options (redirect URIs, grant types, response types, scopes, auth method, client type)
  • Edit client configuration
  • View client details (including redirect URIs, grant types, and creation metadata)
  • Deactivate clients

Sessions

  • Session list with ID, user, IP address, user agent, creation time, expiry, and status (active/expired/deactivated)
  • Filter by user ID or status
  • Deactivate individual sessions (forces logout)
  • View full session details

Settings API

The Admin UI communicates with the server over a dedicated admin API. Settings can also be managed directly via HTTP:

# Read all current settings (sensitive keys omitted)
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:9999/admin/api/settings

# Update settings (hot-reloaded immediately, no restart needed)
curl -X PUT \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mfa_enabled": "true", "mfa_method": "totp"}' \
  http://localhost:9999/admin/api/settings

Building the Admin UI

The pre-built Admin UI is committed to the repository in pkg/admin/dist/ and is embedded at compile time. If you modify the frontend source:

# Build the Admin UI and copy it into the embed directory
make admin-ui-build

# Or build everything (Admin UI + Go binary) in one step
make build

Admin UI Development Mode

# Terminal 1: start the Go server
make run

# Terminal 2: start the Vite dev server (hot-reload)
cd admin-ui && pnpm dev

The dev server runs at http://localhost:5173/admin/ and proxies API requests to the Go server on port 9999.


Account UI

The Account UI is a React SPA (built with React 19, React Router v7, TanStack Query, and Tailwind CSS) embedded into the Go binary via go:embed. It is served at /account/ and lets authenticated users manage their own profile and security settings without involving an administrator.

Authentication is handled automatically: on first visit, users are redirected through the standard OIDC Authorization Code flow using the auto-seeded autentico-account public client, then returned to /account/callback to complete token exchange.

Pages

Dashboard

  • Security status summary — shows whether TOTP is configured and displays the user's username and email
  • Quick link to the Security page for configuration

Profile

  • Edit personal information; visible fields are controlled by backend settings:
    • Username (if allow_username_change is enabled)
    • Email (if allow_email_change is enabled)
    • First name, last name, phone number, profile picture URL, locale
    • Street address, city, state/region, postal code, country

Security

  • Password: Change password with current password verification
  • TOTP (Two-Factor Authentication): Set up TOTP by scanning a QR code in any authenticator app (Google Authenticator, Authy, 1Password, etc.) and confirming with a 6-digit code; disable TOTP with password confirmation
  • Passkeys: Register new passkeys via WebAuthn ceremony, view existing passkeys with creation date, rename or remove any passkey

Sessions

  • List all active OAuth sessions with IP address, user agent, last activity timestamp, and creation time
  • Current session is marked with a badge
  • Revoke any other session individually (forces logout on that device)

Trusted Devices

  • List devices that currently bypass MFA prompts, with device name, last used date, and expiration
  • Revoke any trusted device to require MFA on its next login

Connected Providers

  • List external identity providers (e.g., federated SSO) linked to the account, with provider name, associated email, and connection date
  • Disconnect a provider; lockout prevention blocks disconnection if it is the user's only login method and they have no password

Building the Account UI

The pre-built Account UI is committed to the repository in pkg/account/dist/ and embedded at compile time. If you modify the frontend source:

# Build Account UI and copy assets into the embed directory
make account-ui-build

# Or build everything (Admin UI + Account UI + Go binary) in one step
make build

Account UI Development Mode

# Terminal 1: start the Go server
make run

# Terminal 2: start the Vite dev server (hot-reload)
cd account-ui && pnpm dev

The dev server proxies API requests to the Go server on port 9999.


Endpoints

OIDC Discovery & Identity

Endpoint Method Description
/.well-known/openid-configuration GET OIDC discovery document
/.well-known/jwks.json GET JWK Set — public keys for token verification
/oauth2/certs GET Alias for JWKS (legacy path)
/oauth2/authorize GET Authorization endpoint — renders login page
/oauth2/token POST Token endpoint — code exchange, refresh, ROPC
/oauth2/protocol/openid-connect/token POST Keycloak-compatible token path
/oauth2/userinfo GET/POST UserInfo endpoint
/oauth2/protocol/openid-connect/userinfo GET/POST Keycloak-compatible userinfo path
/oauth2/introspect POST Token introspection (RFC 7662)
/oauth2/revoke POST Token revocation (RFC 7009)
/oauth2/logout POST Session logout

Authentication Flows

Endpoint Method Description
/oauth2/login POST Username/password form submission
/oauth2/mfa GET/POST MFA challenge render and verification
/oauth2/signup GET/POST Self-service user registration (when enabled)
/onboard GET/POST First-run admin onboarding (disabled after first user)
/oauth2/passkey/login/begin GET Begin WebAuthn authentication/registration ceremony
/oauth2/passkey/login/finish POST Complete WebAuthn authentication ceremony
/oauth2/passkey/register/finish POST Complete WebAuthn registration ceremony

Client Registration (Admin Only)

Endpoint Method Description
/oauth2/register POST Register a new OAuth2 client
/oauth2/register GET List all registered clients
/oauth2/register/{client_id} GET Get a specific client
/oauth2/register/{client_id} PUT Update a client's configuration
/oauth2/register/{client_id} DELETE Deactivate a client

Admin API (Admin Auth Required)

Endpoint Method Description
/admin/api/users GET/POST List or create users
/admin/api/users PUT/DELETE Update or deactivate users
/admin/api/users/unlock POST Unlock a locked user account
/admin/api/clients GET/POST/PUT/DELETE Client management
/admin/api/sessions GET/DELETE Session management
/admin/api/stats GET Dashboard statistics
/admin/api/settings GET/PUT Read/update runtime settings

User Self-Service

Endpoint Method Description
/user POST Create a user (for API-driven provisioning)

Account Self-Service API (Bearer Token Required)

Endpoint Method Description
/account/api/settings GET UI configuration (auth mode, field visibility)
/account/api/profile GET / PUT Get or update user profile
/account/api/password POST Change password
/account/api/sessions GET List active OAuth sessions
/account/api/sessions/{id} DELETE Revoke a session
/account/api/passkeys GET List registered passkeys
/account/api/passkeys/register/begin POST Begin WebAuthn registration ceremony
/account/api/passkeys/register/finish POST Complete WebAuthn registration ceremony
/account/api/passkeys/{id} PATCH/DELETE Rename or remove a passkey
/account/api/mfa GET Get MFA status
/account/api/mfa/totp/setup POST Initialize TOTP enrollment (returns QR code)
/account/api/mfa/totp/verify POST Confirm TOTP enrollment with a 6-digit code
/account/api/mfa/totp DELETE Disable TOTP
/account/api/trusted-devices GET List trusted devices
/account/api/trusted-devices/{id} DELETE Revoke a trusted device
/account/api/connected-providers GET List connected external providers
/account/api/connected-providers/{id} DELETE Disconnect an external provider

Supported Grant Types

Grant Type Use Case
authorization_code (+ PKCE) Web apps, SPAs, native apps. The recommended default.
refresh_token Obtaining new access tokens without re-authenticating the user.
password (ROPC) Legacy clients and trusted internal tooling. Use with caution.

Client Interaction Examples

Register an OAuth2 Client (Admin Only)

# Obtain an admin access token
ADMIN_TOKEN=$(curl -s -X POST http://localhost:9999/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&username=admin@example.com&password=AdminPassword123!" \
  | jq -r '.access_token')

# Register a confidential client
curl -X POST http://localhost:9999/oauth2/register \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My Application",
    "redirect_uris": ["https://myapp.com/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "client_type": "confidential",
    "token_endpoint_auth_method": "client_secret_basic"
  }'

The client_secret in the response is shown once. Store it securely — it is bcrypt-hashed in the database and cannot be retrieved.

Register a Public Client (SPA/Mobile)

curl -X POST http://localhost:9999/oauth2/register \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My SPA",
    "redirect_uris": ["http://localhost:3000/callback"],
    "grant_types": ["authorization_code"],
    "client_type": "public",
    "token_endpoint_auth_method": "none"
  }'

Authorization Code Flow (with PKCE)

// Generate PKCE parameters
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return btoa(String.fromCharCode(...array))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

async function generateCodeChallenge(verifier) {
  const data = new TextEncoder().encode(verifier);
  const digest = await crypto.subtle.digest("SHA-256", data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

const codeVerifier = generateCodeVerifier();
sessionStorage.setItem("code_verifier", codeVerifier);

const params = new URLSearchParams({
  response_type: "code",
  client_id: "your_client_id",
  redirect_uri: "https://myapp.com/callback",
  scope: "openid profile email",
  state: crypto.randomUUID(),
  nonce: crypto.randomUUID(),
  code_challenge: await generateCodeChallenge(codeVerifier),
  code_challenge_method: "S256",
});

window.location.href = `http://localhost:9999/oauth2/authorize?${params}`;

Token Exchange

# Confidential client (client_secret_basic — recommended)
curl -X POST http://localhost:9999/oauth2/token \
  -u "your_client_id:your_client_secret" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=YOUR_CODE&redirect_uri=https://myapp.com/callback"

# Public client (PKCE)
curl -X POST http://localhost:9999/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&client_id=your_client_id&code=YOUR_CODE&redirect_uri=https://myapp.com/callback&code_verifier=YOUR_CODE_VERIFIER"

A successful response contains access_token, id_token, refresh_token, token_type, and expires_in.


Security Considerations

As an identity provider, Auténtico is a critical trust boundary. The following considerations apply in production:

  • TLS — always deploy behind a reverse proxy with TLS. Tokens must never travel over plaintext. Set all *_SECURE bootstrap flags to true.
  • Secrets — the .env secrets (AUTENTICO_PRIVATE_KEY, *_SECRET, *_KEY) must be generated fresh per deployment and stored with appropriate access controls (environment injection, secrets manager).
  • CSRFgorilla/csrf protects all browser-facing form endpoints. The CSRF secret must be stable across restarts (so set AUTENTICO_CSRF_SECRET_KEY explicitly in production rather than regenerating it).
  • Redirect URI validation — redirect URIs are strictly matched against registered client URIs. Wildcards are not supported.
  • Client secrets — stored as bcrypt hashes. The plaintext is only available at registration time.
  • Password hashing — user passwords are bcrypt-hashed.
  • Account lockout — configure account_lockout_max_attempts and account_lockout_duration to limit single-account brute-force exposure.
  • Rate limiting — built-in two-tier per-IP rate limiter on /oauth2/login, /oauth2/mfa, /oauth2/token, and /oauth2/passkey/login/finish. A per-second limit (default 5 rps / burst 10) stops rapid bursts; a per-minute limit (default 20 rpm / burst 20) caps sustained enumeration. Set AUTENTICO_RATE_LIMIT_RPS=0 to disable both.
  • RS256 signing — the RSA private key never leaves the server. Relying parties verify tokens using the public JWKS.
  • ROPC scope — restrict the password grant to only clients that genuinely need it by omitting "password" from other clients' grant_types.

Deployment & Operations

Single Binary (Simplest)

./autentico init --url https://auth.example.com
./autentico start

Place an nginx or Caddy instance in front for TLS.

Docker

The included dockerfile produces a minimal Alpine image with nginx for TLS termination. It uses a self-signed certificate by default — replace with a real certificate in production.

docker build -t autentico .
docker run -p 443:443 -p 9999:9999 \
  -e AUTENTICO_APP_URL=https://auth.example.com \
  -e AUTENTICO_PRIVATE_KEY="..." \
  -e AUTENTICO_ACCESS_TOKEN_SECRET="..." \
  -e AUTENTICO_REFRESH_TOKEN_SECRET="..." \
  -e AUTENTICO_CSRF_SECRET_KEY="..." \
  -v /data:/app/db \
  autentico

Docker Compose

docker compose up -d

The provided docker-compose.yml maps ports 9999 and 443.

Reverse Proxy (nginx example)

upstream autentico {
    server 127.0.0.1:9999;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name auth.example.com;

    ssl_certificate /etc/ssl/certs/auth.example.com.crt;
    ssl_certificate_key /etc/ssl/private/auth.example.com.key;

    location / {
        proxy_pass http://autentico;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Backup

# Hot backup (safe while the server is running)
sqlite3 /data/autentico.db ".backup /backup/autentico-$(date +%Y%m%d-%H%M%S).db"

The database is a single file. Back it up like any other file. SQLite's WAL mode (the default) allows consistent hot backups.

Database Migrations

Autentico uses explicit, versioned schema migrations. The binary will refuse to start if the database schema is behind:

database is at version 1, this binary requires version 2 — run: autentico migrate

To apply pending migrations:

autentico migrate

The command shows the current and target versions, warns that migrations are irreversible, and asks you to type the target version number to confirm. Back up your database file before proceeding.

For automated environments (Docker, CI), use the --auto-migrate flag to apply migrations automatically on startup:

autentico start --auto-migrate

Health Check

GET /.well-known/openid-configuration serves as a reliable liveness and readiness probe — it requires the database and key to be functional.

Go Runtime Tuning

# In containerized environments, set GOMAXPROCS explicitly
# Go may not correctly detect container CPU quotas
GOMAXPROCS=4 ./autentico start

# Reduce GC pressure at the cost of higher memory usage
GOGC=200 ./autentico start

SQLite write serialization is the primary throughput bottleneck, not CPU parallelism. GOMAXPROCS primarily benefits concurrent request handling and cryptographic operations.

Migration Path

When SQLite write throughput becomes a constraint (typically > 100k daily active users with high concurrent write peaks), the pkg/db abstraction allows a backend swap. A typical migration path is dual-write (SQLite as source of truth → PostgreSQL as replica) followed by a read cutover and finalization.


Testing

Auténtico maintains comprehensive test coverage with 715+ test functions across unit, integration, and end-to-end tests.

Running Tests

# Run all tests
make test
# Or: go test -p 1 -v ./...

# Run a specific package
go test ./pkg/token/... -v

# Generate HTML coverage report
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html

# Run end-to-end tests only
go test ./tests/e2e/... -v

Tests run with -p 1 (sequential) because they share a process-level SQLite handle. Unit tests use an in-memory database, making them fast and isolated.

Test Categories

  • Unit tests (430+): handler behavior, model validation, service logic, utility functions
  • Integration tests (135+): cross-package flows — authorization, token lifecycle, session management, client authentication
  • End-to-end tests (69+): full HTTP flows against a real test server instance

OIDC Conformance

Auténtico passes the OpenID Foundation oidcc-basic-certification-test-plan — the standard conformance suite for Basic OpenID Providers. The suite covers the full Authorization Code flow: discovery, authorization, token exchange, token refresh, ID token validation, UserInfo, and session management.

# Start Auténtico with conformance-compatible settings (HTTP, no rate limiting)
make conformance-server

# Pull and start the conformance suite at https://localhost:8443
make conformance-suite

API Documentation

Interactive Swagger UI:

make docs
# Opens at http://localhost:8888/swagger/index.html

Static HTML reference:

The pre-generated HTML API reference is available at /docs/index.html in the repository, and hosted at GitHub Pages.


Contributing

Contributions are welcome. Before starting significant work, open an issue to align on approach.

  1. Fork the repository and create a feature branch
  2. Make your changes; follow existing Go conventions and package structure
  3. Add or update tests — make test must pass
  4. Submit a pull request with a clear description of the change and its motivation

License

MIT. See LICENSE for the full text.

About

Autentico is a self-hosted OpenID Connect Identity Provider built with Go. It handles the full authentication lifecycle; login, MFA, passkeys, session management, token issuance, and administration. All in a single binary backed by SQLite.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors