Go authentication library: OAuth 2.1 authorization server, MCP authorization, passkeys, SAML, SCIM and DPoP.
Mount it into your own net/http, chi, Gin, Echo or Fiber server. Your data stays in your own database (memory, SQLite, Postgres or MySQL). Self-hosted, MIT licensed, no hosted service.
Website · Quickstart · Docs · pkg.go.dev · Examples · Discussions · Discord
theauth-go is the Go library for theAuth. It handles sign-in for people (passwords, magic links, social login, passkeys, TOTP, SAML SSO, SCIM provisioning) and for software (an OAuth 2.1 authorization server with PKCE, DPoP, device login, token exchange and agent identities, plus a resource server module for MCP).
It runs inside your process as an http.Handler. You keep your users, sessions and tokens in your own database. The root module is Go 1.25, MIT licensed, and every release is signed and ships an SBOM. The TypeScript library is glincker/theauth.
- Install
- Packages
- Quick start: net/http and SQLite
- Quick start: agent identity and delegation
- What you can build
- Features
- Storage backends
- Stability
- Security
- Migrating
- FAQ
- Examples
go get github.com/glincker/theauth-go/v2
go get github.com/glincker/theauth-go/storage/sqlite # optional, embedded SQLite backendThe module path ends in /v2. The first resolvable v2 tag is v2.6.0, and the old path without /v2 is frozen at v1.0.0. See Migrating to /v2.
Release assets are signed keyless with cosign through GitHub Actions OIDC. Download checksums.txt, checksums.txt.sig and checksums.txt.cert from the release, then verify (replace v2.8.0 with your tag):
cosign verify-blob \
--certificate-identity "https://github.com/glincker/theauth-go/.github/workflows/release.yml@refs/tags/v2.8.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature checksums.txt.sig --certificate checksums.txt.cert checksums.txt
sha256sum --ignore-missing -c checksums.txtEach theauth-go-v*.tar.gz and its .sbom.json also ship with .sig and .cert files that verify the same way.
One Go module at the root plus three nested modules (mcpresource, storage/sqlite, audit/sinks/otlp) that carry their own dependencies. Everything else is a package inside the root module. Full index on pkg.go.dev.
| Group | Packages |
|---|---|
| Core | theauth-go/v2 (server, sessions, OAuth 2.1 authorization server, agent identity, MCP authorization), admin, crypto, email, policy, clientauth (device login for Go CLIs), otp, openapi, mcpresource (separate module) |
| Storage | storage, storage/memory, storage/postgres, storage/mysql, storage/sqlite (separate module), storagetest |
| Audit sinks | audit/sinks/splunkhec, audit/sinks/webhook, audit/sinks/otlp (separate module) |
| Gin adapter | adapters/gin (separate module): native Authn, RequireAuth, RequireAbility, accessors and Mount |
| Echo adapter | adapters/echo (separate module): the same set for Echo v4 |
| Fiber adapter | adapters/fiber (separate module): the same set for Fiber v2 through its net/http adaptor (responses are buffered) |
| Providers | provider/ apple, bitbucket, discord, facebook, github, gitlab, google, linkedin, microsoft, slack, twitch, x, oidc, and generic for 16 more (Spotify, Dropbox, Zoom, Kakao, Naver, Patreon, Box, Salesforce, Figma, Codeberg, Linear, PayPal, Kick, Hugging Face, Vercel, Yandex) |
| Commands | theauth-go (secrets, OpenAPI), theauth-doctor (security doctor), theauth-migrate (Auth0 and Cognito import) |
| Module | Go | Reference |
|---|---|---|
github.com/glincker/theauth-go/v2 |
1.25 | pkg.go.dev |
github.com/glincker/theauth-go/mcpresource |
1.25 | pkg.go.dev |
github.com/glincker/theauth-go/storage/sqlite |
1.26 | pkg.go.dev |
github.com/glincker/theauth-go/adapters/gin |
1.26 | pkg.go.dev |
github.com/glincker/theauth-go/adapters/echo |
1.26 | pkg.go.dev |
github.com/glincker/theauth-go/adapters/fiber |
1.26 | pkg.go.dev |
The TypeScript packages are listed in the theAuth README.
A complete server with sign-up, sign-in, magic links, sessions and one protected route, stored in a single SQLite file. Errors are checked; paste it into a main package. The storage/sqlite module needs Go 1.26.
package main
import (
"context"
"database/sql"
"log"
"net/http"
"github.com/glincker/theauth-go/v2"
sqlitestore "github.com/glincker/theauth-go/storage/sqlite"
_ "modernc.org/sqlite"
)
func main() {
ctx := context.Background()
db, err := sql.Open("sqlite", "file:app.db?_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_txlock=immediate")
if err != nil {
log.Fatal(err)
}
if err := sqlitestore.Migrate(ctx, db); err != nil {
log.Fatal(err)
}
store, err := sqlitestore.New(db)
if err != nil {
log.Fatal(err)
}
a, err := theauth.New(theauth.Config{CoreStorage: store, BaseURL: "http://localhost:8080"})
if err != nil {
log.Fatal(err)
}
defer a.Close()
mux := http.NewServeMux()
mux.Handle("/auth/", a.Handler()) // sign-up, sign-in, magic link, sessions
mux.Handle("GET /me", a.RequireAuth()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, _ := theauth.UserFromContext(r.Context())
w.Write([]byte("hello " + user.Email))
})))
log.Fatal(http.ListenAndServe(":8080", mux))
}For Postgres, chi or a runnable CLI with API tokens and device login, see examples/single-binary-sqlite and the Quick Start.
Mounting: a.Handler() returns a plain http.Handler for net/http's ServeMux and keeps the routes at their configured paths (/auth/..., /oauth/...), so no http.StripPrefix is needed. With chi, call a.Mount(r) on your router instead. Both register the same routes.
Give an agent its own credential, then let it act for a user with a narrower scope. This needs Config.AuthorizationServer (which requires a 32 byte EncryptionKey) and Config.AgentIdentity, on a storage backend with agent identity support (memory, SQLite, Postgres or MySQL). RegisterAgent creates the agent and, because Resource is set, a delegation grant from the user to the agent in one call.
package main
import (
"context"
"log"
"github.com/glincker/theauth-go/v2"
"github.com/glincker/theauth-go/v2/storage/memory"
)
func main() {
ctx := context.Background()
a, err := theauth.New(theauth.Config{
Storage: memory.New(),
BaseURL: "https://as.example.com",
EncryptionKey: []byte("0123456789abcdef0123456789abcdef"), // 32 bytes, load from your secrets manager
AuthorizationServer: &theauth.AuthorizationServerConfig{
Issuer: "https://as.example.com",
Resources: []theauth.ProtectedResource{
{Identifier: "https://api.example.com", Scopes: []string{"read", "write"}},
},
},
AgentIdentity: &theauth.AgentConfig{},
})
if err != nil {
log.Fatal(err)
}
defer a.Close()
// The human who owns the agent. In a real app this is a signed-in user.
user, err := a.UserByID(ctx, ownerID)
if err != nil {
log.Fatal(err)
}
// 1. Create the agent and a delegation grant for "read" on one resource.
reg, err := a.RegisterAgent(ctx, theauth.RegisterAgentInput{
OwnerID: user.ID,
Name: "report-bot",
Scope: []string{"read"},
Resource: "https://api.example.com",
})
if err != nil {
log.Fatal(err)
}
// reg.Secret.ClientID and reg.Secret.Secret are the agent's credential.
// The secret is shown once and only its Argon2id hash is stored.
// 2. The agent authenticates as itself (client credentials).
self, err := a.ClientCredentialsToken(ctx, theauth.TokenRequest{
GrantType: "client_credentials",
ClientID: reg.Secret.ClientID,
ClientSecret: reg.Secret.Secret,
Resource: "https://api.example.com",
Scope: []string{"read"},
})
if err != nil {
log.Fatal(err)
}
_ = self.AccessToken // sub is "agent:<id>"
// 3. The agent trades a user's access token for a delegated one
// (RFC 8693). The result carries the user as sub and the agent in act.
delegated, err := a.ExchangeToken(ctx, theauth.TokenExchangeRequest{
ClientID: reg.Secret.ClientID,
ClientSecret: reg.Secret.Secret,
SubjectToken: userAccessToken, // issued to the user by this authorization server
SubjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
Resource: "https://api.example.com",
Scope: []string{"read"},
})
if err != nil {
log.Fatal(err)
}
log.Println("delegated token expires in", delegated.ExpiresIn, "seconds")
}ownerID and userAccessToken stand in for values from your sign-in flow (the user's ULID and an access token the authorization server issued to that user). Revoke access at any point with RevokeDelegation, SuspendAgent or RevokeAgent; resource servers see the change on their next introspection. Details: Agent identity and revocation, Agent delegation and Authorization server.
Email and password, magic links, 12 OAuth login providers, generic OIDC and 16 more catalog providers, with server-side sessions. Start with the net/http quick start or the Gin, Echo and Fiber adapters. Guide: Add an OAuth provider.
Authorization code with PKCE, refresh rotation, client_credentials, token exchange, PAR, JAR, CIBA and DPoP, with dynamic client registration and client ID metadata documents. Guide: Authorization server.
Use theauth-go as the authorization server for MCP clients, and the separate mcpresource module to validate tokens and guard tools in your MCP resource server. Example: examples/mcp-server. Guides: MCP authorization, Protect MCP tools.
Discoverable passkey login and TOTP with recovery codes, plus step-up checks with RequireRecentAuth. Example: examples/webauthn-passkey. Guide: Enable passkeys.
RFC 8628 device authorization on the server and the clientauth package in your CLI. Example: examples/cli-login. Guide: CLI login.
SAML 2.0 single sign-on, SCIM 2.0 user provisioning, organizations, RBAC and an append-only audit log with Splunk, webhook and OTLP sinks. Needs memory, SQLite, Postgres or MySQL storage. Guide: Splunk audit streaming.
CreateUser adds a user row without sending email or creating a credential or session. It is idempotent on the normalized email, so calling it on every login of an external identity provider is safe, even concurrently. Typed errors match ErrUserInvalidInput, ErrUserEmailConflict and ErrUserIDConflict with errors.Is. Details: docs/create-user.md.
u, err := a.CreateUser(ctx, theauth.CreateUserInput{Email: "ada@example.com", Name: "Ada"})Give an AI agent its own credential and let it act for a user with a narrower scope. See the agent quick start.
| Area | What you get | Docs |
|---|---|---|
| Sign-in | Email and password (Argon2id, 12 character minimum), magic links, 12 OAuth login providers, generic OIDC | Add an OAuth provider |
| Second factors | WebAuthn passkeys with discoverable login, TOTP with recovery codes, step-up with RequireRecentAuth |
Enable passkeys |
| Sessions | Opaque server-side sessions, session list and revoke, idle timeout | Configuration |
| API tokens and CLIs | Scoped tokens with RequireAbility, RFC 8628 device login via the clientauth package |
API tokens, CLI login |
| OAuth 2.1 server | Authorization code with PKCE, refresh rotation, client_credentials, token exchange, CIBA, PAR, JAR, DPoP |
OAuth 2.1 primer, Authorization server |
| Token policy, RAR and ID-JAG | Per-client signing algorithm (EdDSA, ES256, RS256) and opaque access tokens, RFC 9396 authorization_details end to end, and ID-JAG issue and redeem over token exchange and jwt-bearer |
Token policy, RAR, ID-JAG |
| OpenID Provider | ID tokens with nonce and at_hash, userinfo, RP-initiated and back-channel logout, pairwise subjects, OpenID discovery fields |
OpenID Provider |
| Multi-replica and abuse controls | Pluggable rate limiter, DPoP replay cache and CIMD cache (memory, SQL, Redis or your own), per-IP and per-client limits and an Argon2id concurrency cap on the OAuth endpoints | Pluggable stores, Endpoint limits, Request admission |
| Browserless OAuth | RFC 8628 device authorization grant on the authorization server with a themable verification page, plus scoped one-time registration tokens for agents | Device grant, Registration tokens |
| One-time codes | Email and SMS OTP with pluggable senders (Twilio built in), resend cooldown, attempt limit, lockout, constant-time compare | OTP |
| More sign-in providers | Table-driven catalog of 16 more OAuth providers, custom specs, and OIDC discovery overrides | Provider catalog |
| Developer CLI and OpenAPI | theauth-go secret and theauth-go openapi, plus openapi.Generate for your own router |
CLI |
| MCP and agents | MCP authorization (protected resource metadata, CIMD), agent identities with delegation, mcpresource SDK module (scope and step-up guards, tool security metadata) |
MCP authorization, Resource server, Protect MCP tools |
| Enterprise | SAML 2.0 SSO, SCIM 2.0, organizations, RBAC, append-only audit log with SIEM sinks | Splunk audit streaming, Threat model |
| Policy | Authorization policy engine | Policy engine |
| Observability | OpenTelemetry spans, Prometheus metrics | OpenTelemetry tracing, Metrics |
| Supply chain | SBOM and Sigstore signed release artifacts, SLSA 3 provenance | Releases and verification |
Rate limits, DPoP replay records and the CIMD cache are per process by default. Share them through the database you already have:
db := stdlib.OpenDBFromPool(pool) // pgx stdlib
kvStore, _ := sqlkv.New(db, sqlkv.Postgres)
_ = kvStore.EnsureSchema(ctx)
auth, _ := theauth.New(theauth.Config{
// ...
Stores: kv.FromCache(kvStore), // or kv/redis, or your own kv.RateLimiter / kv.ReplayCache / kv.Cache
})Details and the Redis adapter are in docs/pluggable-stores.md.
theauth-go is a library you embed and run yourself, so you own the database and the operations. It does not include a hosted login UI or a managed service. If you need either, it is not the right shape for you. The TypeScript library at glincker/theauth covers Node, Bun, Deno and Workers backends.
Persistence is split into small capability interfaces, and New fails at startup if a feature you enabled has no matching capability. Full matrix: Capability interfaces.
| Capability | memory | sqlite | postgres | mysql |
|---|---|---|---|---|
| Users, sessions, magic links, passwords | Yes | Yes | Yes | Yes |
| OAuth accounts, passkeys, TOTP, audit log | Yes | Yes | Yes | Yes |
| Scoped API tokens, device grant, session management | Yes | Yes | Yes | Yes |
| Organizations, SAML, SCIM, RBAC | Yes | Yes | Yes | Yes |
| OAuth 2.1 authorization server, agent identity | Yes | Yes | Yes | Yes |
| CIBA backchannel auth | Yes | Yes | Yes | No |
| OAuth device grant (RFC 8628) and registration tokens | Yes | Yes | Yes | Yes |
Durable JWT-bearer jti replay |
Yes | Yes | No | No |
Postgres and MySQL implement the newer token, device, session and throttle capabilities and pass the new storagetest suites against live PostgreSQL 16 and MySQL 8. The full shared contract passes on Postgres when you opt in with THEAUTH_PG_CONTRACT=1 and a DSN in THEAUTH_TEST_PG_DSN (verified on PostgreSQL 14), but that gate is not enabled in CI yet. The MySQL gate is still off, so treat MySQL support for the newer features as newly added. You can write your own backend and verify it with the public storagetest suite.
Packages and APIs that were Stable before v2.6 keep their SemVer guarantees. Among the additions in v2.6, only the storage capability split, Handler() and Config.PathPrefix are Stable. Experimental: storage/sqlite, clientauth, policy, the agent identity and revocation APIs, Doctor, Config.ProviderResolver and the new optional storage capabilities. Experimental APIs may change in a minor release. The SemVer rules and the list of stable packages are in STABILITY.md.
- Passwords are hashed with Argon2id, sessions are opaque and stored server side, and agent secrets are shown once and stored only as hashes.
- Release assets are signed with cosign, ship an SBOM and carry SLSA 3 provenance. See Verify releases.
- CI runs CodeQL,
govulncheck, dependency review and OpenSSF Scorecard. - The threat model and the
theauth-doctorsecurity doctor describe what is covered and how to check a deployment. - Report vulnerabilities privately through GitHub advisories or support@glinr.com, as described in the security policy. Do not open a public issue.
- From Auth0: Migrate from Auth0
- From Cognito: Migrate from Cognito
- From Keycloak: Migrate from Keycloak
- From hand-rolled sessions: Migrate from hand-rolled sessions
- From the v1 module path: Migrating to /v2
Does theauth-go work without Postgres?
Yes. Use storage/sqlite for a single static binary with one database file, storage/memory for tests, or MySQL. SQLite supports organizations, SAML, SCIM and RBAC, and runs the OAuth 2.1 authorization server, agents and CIBA (migrations 0020 to 0034); see docs/storage-capabilities.md.
How do I add passkeys to a Go app?
Set Config.WebAuthn and use a storage backend that implements WebAuthnStorage (all four built in backends do). Register and login routes are then mounted under your auth handler. See the passkeys guide and examples/webauthn-passkey.
Does it support MCP authorization?
Yes. The library can act as the OAuth 2.1 authorization server and publishes protected resource metadata. The separate zero dependency mcpresource module validates tokens inside an MCP resource server. See MCP authorization and examples/mcp-server.
How do I migrate from Auth0, Cognito or Keycloak? Follow Migrate from Auth0, Migrate from Cognito or Migrate from Keycloak. Password hashes carry over and upgrade to Argon2id at each user's first login. Coming from hand-rolled sessions: Migrate from hand-rolled sessions.
Which Go versions are supported?
The root module declares Go 1.25. storage/sqlite is a separate module and needs Go 1.26.
Is it a hosted service? No. It is a library you run inside your own process, under the MIT license.
Runnable apps live in examples/, each with its own go.mod and a short README. More in the example apps guide.
| Area | Examples |
|---|---|
| Frameworks | single-binary-sqlite, chi-app, gin-app, echo-app, stdlib-app |
| OAuth providers | oauth-multi-provider, oauth-apple, oauth-bitbucket, oauth-facebook, oauth-gitlab, oauth-linkedin, oauth-slack, oauth-twitch, oauth-x |
| Second factors | webauthn-passkey, totp-stepup |
| Agents and CLIs | mcp-server, cli-login |
| Operations | doctor-admin, observability-otel, observability-prom |
- Documentation and API reference on pkg.go.dev
- CHANGELOG, STABILITY, ROADMAP
- Security policy: report vulnerabilities privately to support@glinr.com or through GitHub advisories, not in public issues
- Contributing, Discussions, GLINR Discord
- AGENTS.md and llms.txt for AI coding assistants
- License: MIT
theAuth is built and maintained in the open by GLINCKER, the open-source division of GLINR STUDIOS. Founder: thegdsks.com. The TypeScript library is glincker/theauth.
| Partner project | What it is | Site | Source |
|---|---|---|---|
| LevelRail | Self-hosted deployment platform: push to git, get a running app. | levelrail.com | glincker/levelrail |
| theSVG | Open-source brand SVG icons. | thesvg.org | glincker/thesvg |
| GLINUI | Open-source liquid glass UI components for React. theauth.dev is designed with it. | glinui.com | glincker/glinui |