Design Decisions
Architecture Philosophy
Section titled “Architecture Philosophy”Autentico’s central design goal is operational simplicity. Every significant architectural decision — the database, the signing algorithm, the configuration model, the cleanup strategy — was evaluated against a single question: does this reduce or increase the burden on the person running this?
Most identity platforms require you to operate several services before users can log in: a database server, a cache, worker processes, and the identity service itself. Each component adds configuration, monitoring, failure modes, and backup requirements. Autentico eliminates this stack. The entire system is one binary and one SQLite file. There is no connection pool to tune, no external credentials to rotate for the storage layer, and no network partition between the IdP and its database.
The protocol and security surface are not simplified. Autentico implements OAuth2 and OpenID Connect to spec. The simplicity is in the operational model, not in what the system does.
The sections below explain each individual decision: what was chosen, why, and what the tradeoffs are.
SQLite as the database
Section titled “SQLite as the database”Autentico uses SQLite (via modernc.org/sqlite — a pure-Go implementation, no CGo) rather than a separate database server.
Why:
- Zero operational overhead — no database server to deploy, monitor, or back up separately
- A single file to manage — the entire state of the system is in
autentico.db - SQLite handles concurrent reads well and serializes writes safely
- For a typical IdP workload (user authentication, not analytics), SQLite scales to tens of thousands of users and hundreds of authentications per second
- Most OIDC deployments don’t need horizontal scaling of the auth server itself — if you do, you’re in a different scale class and will need to replace the persistence layer
The tradeoff: you can’t run multiple Autentico instances pointing at the same SQLite file. For high-availability setups, use a load balancer with sticky sessions pointing to a single Autentico instance, or migrate to a PostgreSQL backend (not currently implemented).
RS256 for token signing
Section titled “RS256 for token signing”Tokens are signed with RSA-SHA256 (asymmetric).
Why:
- Resource servers can verify tokens using only the public key (from
/oauth2/.well-known/jwks.json) — they never need access to the private key or to make a network call to Autentico per request - OIDC Discovery mandates JWKS exposure; RS256 is the most widely supported algorithm
- The alternative, HS256 (symmetric), would require sharing the signing secret with every resource server — a credential distribution problem
The RSA key is 2048-bit. A new key pair for production is generated with autentico init, which outputs a base64-encoded PEM suitable for the AUTENTICO_PRIVATE_KEY environment variable.
Three-layer configuration
Section titled “Three-layer configuration”Settings are split across three layers: bootstrap (.env), runtime (settings DB), and per-client (clients table).
Why:
- Bootstrap settings affect server startup behavior (port, DB path, TLS key path) — these legitimately require a restart to change and are better modeled as environment variables consistent with 12-factor app conventions
- Runtime settings affect behavior that operators need to change without downtime (enabling MFA, adjusting token lifetimes, theming) — storing them in the DB and hot-reloading is the right model
- Per-client overrides allow a single Autentico instance to serve multiple applications with different security policies — a strict banking app can have short session timeouts while a general consumer app has longer ones
The alternative (a single config file or all-env) would require either a restart for every settings change or complex file-watching logic. The DB + hot-reload approach is simpler and more reliable.
No external dependencies for token issuance
Section titled “No external dependencies for token issuance”JWT generation and signing are done with the standard library (crypto/rsa, encoding/base64, crypto/sha256) and a minimal JWT library. There’s no dependency on a third-party OAuth2 framework.
Why:
- OAuth2 and OIDC are well-defined protocols — implementing them correctly from the spec is straightforward and results in code you own and understand
- Third-party OAuth2 servers (Keycloak, Hydra) carry significant operational complexity; Autentico’s goal is to be the simple alternative
- Fewer dependencies means fewer supply chain attack surfaces
Background cleanup goroutine
Section titled “Background cleanup goroutine”Expired records (auth codes, MFA challenges, passkey challenges, sessions, tokens, trusted devices) are purged by a background goroutine that runs on a configurable interval.
Why:
- SQLite doesn’t have TTL-based auto-expiry
- Cleanup in-band with requests (on read) would add latency and complexity to the hot path
- A separate goroutine running every N minutes is simple, observable (logs its work), and keeps the database from growing unboundedly
The interval and retention window are configurable via cleanup_interval and cleanup_retention settings.
Embedded Admin UI and Swagger docs
Section titled “Embedded Admin UI and Swagger docs”The React admin UI and Swagger docs are embedded in the Go binary using //go:embed.
Why:
- Single binary deployment — no need to separately deploy or serve static files
- The binary is self-contained and versioned as a unit
- The tradeoff is a larger binary size, which is acceptable for an IdP that runs as a long-lived process