glincker/theauth-go

Authentication and authorization library for Go: password, magic link, OAuth 2.1/OIDC, passkeys (WebAuthn), TOTP, SAML, SCIM, API tokens, device login, agent identity and MCP authorization. SQLite, Postgres, MySQL.

Go

5

309 commits

updated Oct 10, 2026

See the code

See what people are saying

SourceMessageScoreDate

theAuth: open-source auth where AI agents are first-class, not just API keys (r/webdev)

**theAuth: open-source auth for AI agents and humans (TypeScript and Go), built solo** [https://github.com/glincker/theauth](https://github.com/glincker/theauth) **theAuth** is auth for apps where AI agents act on behalf of users. Each agent gets its own token and permissions, a user can delegate…

0

Oct 10, 2026

README

theauth-go

Auth for AI agents and humans in Go: MCP OAuth 2.1 server, agent identity and delegation, DPoP, passkeys, device flow. Mount it into your own net/http or chi server. Your data stays in your own database (memory, SQLite, Postgres or MySQL). MIT licensed, no hosted service.

Go Reference Release CI codecov Discord License: MIT SLSA 3 CodeQL Context7

Website · Docs · pkg.go.dev · Packages · theAuth for TypeScript (TypeScript packages)

This is the Go SDK for theAuth, open-source auth for AI agents and humans. The TypeScript library lives in glincker/theauth and this repo is the Go counterpart. Both share one model: agents as identities, scoped permissions, delegation chains and an audit trail.

Contents

Install

go get github.com/glincker/theauth-go/v2
go get github.com/glincker/theauth-go/storage/sqlite   # optional, embedded SQLite backend

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

Verify releases

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

Each theauth-go-v*.tar.gz and its .sbom.json also ship with .sig and .cert files that verify the same way.

Packages

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.

GroupPackages
Coretheauth-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)
Storagestorage, storage/memory, storage/postgres, storage/mysql, storage/sqlite (separate module), storagetest
Audit sinksaudit/sinks/splunkhec, audit/sinks/webhook, audit/sinks/otlp (separate module)
Providersprovider/ apple, bitbucket, discord, facebook, github, gitlab, google, linkedin, microsoft, slack, twitch, x, oidc, and generic for 10 more (Spotify, Dropbox, Zoom, Kakao, Naver, Patreon, Box, Salesforce, Figma, Codeberg)
Commandstheauth-go (secrets, OpenAPI), theauth-doctor (security doctor), theauth-migrate (Auth0 and Cognito import)

The TypeScript packages are listed in the theAuth README.

Quick start: net/http and SQLite

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.

Quick start: agent identity and delegation

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, Postgres or MySQL; not SQLite). 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.

Features

AreaWhat you getDocs
Sign-inEmail and password (Argon2id, 12 character minimum), magic links, 12 OAuth login providers, generic OIDCAdd an OAuth provider
Second factorsWebAuthn passkeys with discoverable login, TOTP with recovery codes, step-up with RequireRecentAuthEnable passkeys
SessionsOpaque server-side sessions, session list and revoke, idle timeoutConfiguration
API tokens and CLIsScoped tokens with RequireAbility, RFC 8628 device login via the clientauth packageAPI tokens, CLI login
OAuth 2.1 serverAuthorization code with PKCE, refresh rotation, client_credentials, token exchange, CIBA, PAR, JAR, DPoPOAuth 2.1 primer, Authorization server
Token policy, RAR and ID-JAGPer-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-bearerToken policy, RAR, ID-JAG
Multi-replica and abuse controlsPluggable 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 endpointsPluggable stores, Endpoint limits
Browserless OAuthRFC 8628 device authorization grant on the authorization server with a themable verification page, plus scoped one-time registration tokens for agentsDevice grant, Registration tokens
One-time codesEmail and SMS OTP with pluggable senders (Twilio built in), resend cooldown, attempt limit, lockout, constant-time compareOTP
More sign-in providersTable-driven catalog of 10 more OAuth providers, custom specs, and OIDC discovery overridesProvider catalog
Developer CLI and OpenAPItheauth-go secret and theauth-go openapi, plus openapi.Generate for your own routerCLI
MCP and agentsMCP authorization (protected resource metadata, CIMD), agent identities with delegation, mcpresource SDK moduleMCP authorization, Resource server
EnterpriseSAML 2.0 SSO, SCIM 2.0, organizations, RBAC, append-only audit log with SIEM sinksSplunk audit streaming, Threat model
PolicyAuthorization policy enginePolicy engine
ObservabilityOpenTelemetry spans, Prometheus metricsOpenTelemetry tracing, Metrics
Supply chainSBOM and Sigstore signed release artifacts, SLSA 3 provenanceReleases and verification

Running more than one replica

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.

How it compares

Short version, based on each project's public docs. Verify before you decide.

  • Auth0, Clerk, Cognito: managed services with hosted UIs and support contracts. theauth-go is a library you embed and run yourself, so you own the database and the ops work. Migration tools for Auth0 and Cognito are included.
  • Better Auth: a TypeScript library with a large plugin ecosystem. If your backend is TypeScript, use the TypeScript SDK or Better Auth. theauth-go is for Go services.
  • Ory, Keycloak: standalone servers you deploy and talk to over HTTP. theauth-go runs in your process. You give up their admin UIs and larger feature surface.
  • Rolling your own on golang.org/x/oauth2 and a session table: fine for one provider. Gets expensive once you add passkeys, device login, token exchange and revocation.

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

Capabilitymemorysqlitepostgresmysql
Users, sessions, magic links, passwordsYesYesYesYes
OAuth accounts, passkeys, TOTP, audit logYesYesYesYes
Scoped API tokens, device grant, session managementYesYesYesYes
Organizations, SAML, SCIM, RBACYesNoYesYes
OAuth 2.1 authorization server, agent identityYesNoYesYes
CIBA backchannel authYesNoYesNo
OAuth device grant (RFC 8628) and registration tokensYesNoYesYes
Durable JWT-bearer jti replay, policy engine storageYesNoNoNo

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 older shared contract gate for those two adapters is still off in CI because some older subtests fail, so treat their support for the newer features as newly added. You can write your own backend and verify it with the public storagetest suite.

Stability

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.

FAQ

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 has no organizations, SAML, SCIM, RBAC or OAuth authorization server.

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.

Examples

Runnable apps live in examples/, each with its own go.mod and a short README. More in the example apps guide.

Made by GLINCKER, a GLINR STUDIOS company

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 projectWhat it isSiteSource
LevelRailSelf-hosted deployment platform: push to git, get a running app.levelrail.comglincker/levelrail
theSVGOpen-source brand SVG icons.thesvg.orgglincker/thesvg
GLINUIOpen-source liquid glass UI components for React. theauth.dev is designed with it.glinui.comglincker/glinui
authentication
authorization-server
go
golang
identity-provider
jwt
magic-link
mcp
mcp-authorization
oauth2
oauth-server
oidc
passkeys
pkce
rbac
saml
scim
session-management
totp
webauthn

glincker/theauth-go

Authentication and authorization library for Go: password, magic link, OAuth 2.1/OIDC, passkeys (WebAuthn), TOTP, SAML, SCIM, API tokens, device login, agent identity and MCP authorization. SQLite, Postgres, MySQL.

Go

5

309 commits

updated Oct 10, 2026

See the code

See what people are saying

SourceMessageScoreDate

theAuth: open-source auth where AI agents are first-class, not just API keys (r/webdev)

**theAuth: open-source auth for AI agents and humans (TypeScript and Go), built solo** [https://github.com/glincker/theauth](https://github.com/glincker/theauth) **theAuth** is auth for apps where AI agents act on behalf of users. Each agent gets its own token and permissions, a user can delegate…

0

Oct 10, 2026

README

theauth-go

Auth for AI agents and humans in Go: MCP OAuth 2.1 server, agent identity and delegation, DPoP, passkeys, device flow. Mount it into your own net/http or chi server. Your data stays in your own database (memory, SQLite, Postgres or MySQL). MIT licensed, no hosted service.

Go Reference Release CI codecov Discord License: MIT SLSA 3 CodeQL Context7

Website · Docs · pkg.go.dev · Packages · theAuth for TypeScript (TypeScript packages)

This is the Go SDK for theAuth, open-source auth for AI agents and humans. The TypeScript library lives in glincker/theauth and this repo is the Go counterpart. Both share one model: agents as identities, scoped permissions, delegation chains and an audit trail.

Contents

Install

go get github.com/glincker/theauth-go/v2
go get github.com/glincker/theauth-go/storage/sqlite   # optional, embedded SQLite backend

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

Verify releases

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

Each theauth-go-v*.tar.gz and its .sbom.json also ship with .sig and .cert files that verify the same way.

Packages

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.

GroupPackages
Coretheauth-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)
Storagestorage, storage/memory, storage/postgres, storage/mysql, storage/sqlite (separate module), storagetest
Audit sinksaudit/sinks/splunkhec, audit/sinks/webhook, audit/sinks/otlp (separate module)
Providersprovider/ apple, bitbucket, discord, facebook, github, gitlab, google, linkedin, microsoft, slack, twitch, x, oidc, and generic for 10 more (Spotify, Dropbox, Zoom, Kakao, Naver, Patreon, Box, Salesforce, Figma, Codeberg)
Commandstheauth-go (secrets, OpenAPI), theauth-doctor (security doctor), theauth-migrate (Auth0 and Cognito import)

The TypeScript packages are listed in the theAuth README.

Quick start: net/http and SQLite

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.

Quick start: agent identity and delegation

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, Postgres or MySQL; not SQLite). 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.

Features

AreaWhat you getDocs
Sign-inEmail and password (Argon2id, 12 character minimum), magic links, 12 OAuth login providers, generic OIDCAdd an OAuth provider
Second factorsWebAuthn passkeys with discoverable login, TOTP with recovery codes, step-up with RequireRecentAuthEnable passkeys
SessionsOpaque server-side sessions, session list and revoke, idle timeoutConfiguration
API tokens and CLIsScoped tokens with RequireAbility, RFC 8628 device login via the clientauth packageAPI tokens, CLI login
OAuth 2.1 serverAuthorization code with PKCE, refresh rotation, client_credentials, token exchange, CIBA, PAR, JAR, DPoPOAuth 2.1 primer, Authorization server
Token policy, RAR and ID-JAGPer-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-bearerToken policy, RAR, ID-JAG
Multi-replica and abuse controlsPluggable 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 endpointsPluggable stores, Endpoint limits
Browserless OAuthRFC 8628 device authorization grant on the authorization server with a themable verification page, plus scoped one-time registration tokens for agentsDevice grant, Registration tokens
One-time codesEmail and SMS OTP with pluggable senders (Twilio built in), resend cooldown, attempt limit, lockout, constant-time compareOTP
More sign-in providersTable-driven catalog of 10 more OAuth providers, custom specs, and OIDC discovery overridesProvider catalog
Developer CLI and OpenAPItheauth-go secret and theauth-go openapi, plus openapi.Generate for your own routerCLI
MCP and agentsMCP authorization (protected resource metadata, CIMD), agent identities with delegation, mcpresource SDK moduleMCP authorization, Resource server
EnterpriseSAML 2.0 SSO, SCIM 2.0, organizations, RBAC, append-only audit log with SIEM sinksSplunk audit streaming, Threat model
PolicyAuthorization policy enginePolicy engine
ObservabilityOpenTelemetry spans, Prometheus metricsOpenTelemetry tracing, Metrics
Supply chainSBOM and Sigstore signed release artifacts, SLSA 3 provenanceReleases and verification

Running more than one replica

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.

How it compares

Short version, based on each project's public docs. Verify before you decide.

  • Auth0, Clerk, Cognito: managed services with hosted UIs and support contracts. theauth-go is a library you embed and run yourself, so you own the database and the ops work. Migration tools for Auth0 and Cognito are included.
  • Better Auth: a TypeScript library with a large plugin ecosystem. If your backend is TypeScript, use the TypeScript SDK or Better Auth. theauth-go is for Go services.
  • Ory, Keycloak: standalone servers you deploy and talk to over HTTP. theauth-go runs in your process. You give up their admin UIs and larger feature surface.
  • Rolling your own on golang.org/x/oauth2 and a session table: fine for one provider. Gets expensive once you add passkeys, device login, token exchange and revocation.

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

Capabilitymemorysqlitepostgresmysql
Users, sessions, magic links, passwordsYesYesYesYes
OAuth accounts, passkeys, TOTP, audit logYesYesYesYes
Scoped API tokens, device grant, session managementYesYesYesYes
Organizations, SAML, SCIM, RBACYesNoYesYes
OAuth 2.1 authorization server, agent identityYesNoYesYes
CIBA backchannel authYesNoYesNo
OAuth device grant (RFC 8628) and registration tokensYesNoYesYes
Durable JWT-bearer jti replay, policy engine storageYesNoNoNo

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 older shared contract gate for those two adapters is still off in CI because some older subtests fail, so treat their support for the newer features as newly added. You can write your own backend and verify it with the public storagetest suite.

Stability

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.

FAQ

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 has no organizations, SAML, SCIM, RBAC or OAuth authorization server.

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.

Examples

Runnable apps live in examples/, each with its own go.mod and a short README. More in the example apps guide.

Made by GLINCKER, a GLINR STUDIOS company

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 projectWhat it isSiteSource
LevelRailSelf-hosted deployment platform: push to git, get a running app.levelrail.comglincker/levelrail
theSVGOpen-source brand SVG icons.thesvg.orgglincker/thesvg
GLINUIOpen-source liquid glass UI components for React. theauth.dev is designed with it.glinui.comglincker/glinui
authentication
authorization-server
go
golang
identity-provider
jwt
magic-link
mcp
mcp-authorization
oauth2
oauth-server
oidc
passkeys
pkce
rbac
saml
scim
session-management
totp
webauthn

Project health

Overall

7.4/10

Maintained

10/10

Code review

0/10

Contributors

10/10
OpenSSF Scorecard

Latest release

v1.0.0 · Jun 21, 2026

Releases this year

9