Zero-Ceremony Identity: Why I Built a Single-Binary OIDC Provider in Go
Source: Dev.to
The problem with existing solutions
Identity infrastructure is notoriously complex. A typical self‑hosted setup involves:
- A database server
- A cache tier (e.g., Redis)
- A worker queue
- The identity service itself
Running a lightweight OIDC server on a small 2 GB RAM VPS quickly revealed that the existing landscape was either operationally exhausting or structurally flawed for my needs.
What I found lacking
| Provider | Issue |
|---|---|
| Casdoor | Demo instances recycle accounts every 5 minutes, making it impossible to test account deletion. |
| PocketId | Passkey‑only by default. Users on older OSes or restrictive browsers would be locked out. |
These roadblocks pushed me to convert my OIDC protocol server (originally built for a frontend library at work) into a full IdP, constantly asking: Does this reduce or increase the operational burden on the operator?
Auténtico’s design principles
- Single binary – The entire IdP runs as one Go binary.
- Embedded SQLite – All state lives in a single file; no external DB server, eliminating connection‑pool tuning, credential rotation, and network partitions.
- Zero external infrastructure – No Redis, Postgres, or message queues. Background goroutines automatically purge expired tokens, sessions, and auth codes.
- Embedded UIs – Admin dashboard (React/Ant Design) and user‑facing Account UI (React/Tailwind) are compiled into the binary via
go:embed. No separate frontend deployments.
Flexible authentication modes
Auténtico offers three runtime‑switchable authentication modes (no restart required):
passwordpassword_and_passkeypasskey_only
If a deployment using passkey_only encounters browser incompatibilities, an admin can instantly flip a setting in the Admin UI to fall back to passwords. Additional fallback methods include:
- TOTP (in‑browser QR enrollment)
- Email OTP
- Hardware‑backed FIDO2 authentication (with seamless first‑login registration)
Vertical‑slice architecture
The codebase follows a strict vertical‑slice layout, where each package owns its slice of functionality:
pkg/login/
model.go
handler.go
service.go
// Database CRUDThis disciplined structure:
- Keeps the code “deliberately un‑clever,” making it easy for AI assistants to generate boilerplate.
- Enabled rapid creation of > 700 tests, achieving ~80 % coverage.
Performance & scalability
SQLite serializes writes, so Auténtico is not meant for active‑active multi‑region deployments. The performance envelope is nevertheless generous for internal tools and small‑to‑mid‑size apps.
| Concurrent VUs | Error rate | Login p95 | Token p95 | Verdict |
|---|---|---|---|---|
| 20 | 0 % | 86 ms | 54 ms | Comfortable – imperceptible |
| 100 | 0 % | 611 ms | 647 ms | Supported – fully functional |
| 500 | 0 % | 3.36 s | 3.89 s | Degraded – noticeable latency |
Tests were run with k6; SQLite’s busy timeout queues requests, adding latency instead of throwing errors.
For most teams, trading infinite horizontal scaling for zero operational overhead is the right choice.
Feature checklist
- OIDC Discovery – Publishes
/.well-known/openid-configuration. - JWK Set – Exposes public signing keys at
/.well-known/jwks.json. - RS256 JWT signing – Asymmetric signing; the private key never leaves the IdP.
- OAuth2/OIDC protocol implementation.
- Admin UI – Manage clients, users, and sessions.
- Account UI – Users manage their profile.
- Swagger/OpenAPI docs – API specifications are published automatically.
Who should consider Auténtico?
- Small teams or indie developers needing an Identity Provider without a dedicated sysadmin.
- Organizations running internal tools, self‑hosted environments, or modest‑scale applications where operational simplicity outweighs massive horizontal scaling.
GitHub: