Releases: glincker/theauth-go
v2.5.0-rc.1
Changelog
New features
- : feat(hooks): Config.LifecycleHooks surface (OnSignup, OnSignin, ...) + UserByID (#76, #77) (#83) (@thegdsks)
- : feat(hooks): wire OnSignup + OnSignin at OAuth callback (#76) (#86) (@thegdsks)
- : feat(hooks): wire OnSignup + OnSignin at magic-link consume (#76) (#85) (@thegdsks)
- : feat(tenancy): Config.Tenancy auto-provision personal org on signup (#77) (#87) (@thegdsks)
Bug fixes
Other changes
- : chore(release): prepare v2.5.0-rc.1 (#88) (@thegdsks)
- : ci: bench workflow to manual + weekly (was per-PR, ~15min burn) (#75) (@thegdsks)
- : docs(readme): replace primitive ASCII with ANSI Shadow logo (#72) (@thegdsks)
- : refactor(root): consolidate root files for cleaner repo home page (#71) (@thegdsks)
Verify this release
# Download the SBOM and its Sigstore signature from the release assets, then:
cosign verify-blob \
--certificate-identity "https://github.com/glincker/theauth-go/.github/workflows/release.yml@refs/tags/v2.5.0-rc.1" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature theauth-go-v2.5.0-rc.1.tar.gz.sbom.json.sig \
--certificate theauth-go-v2.5.0-rc.1.tar.gz.sbom.json.cert \
theauth-go-v2.5.0-rc.1.tar.gz.sbom.jsonUse this library: go get github.com/glincker/theauth-go@v2.5.0-rc.1
v2.4.0
theauth-go v2.4.0
The enterprise security profile, supply chain hardening, and storage portability release.
v2.4 closes the FAPI 2.0 baseline by combining PAR (RFC 9126), JAR (RFC 9101), and
JWT-Bearer client authentication (RFC 7523). A new MySQL 8.x backend, public storagetest
contract suite, CIBA backchannel authentication (RFC 9509), and a Cognito/Auth0 CLI
migration tool round out the release. All additions are fully additive: downstream
code compiles unchanged.
Highlights
FAPI 2.0 baseline (PAR + JAR + JWT-Bearer)
PAR (#64) pushes the full authorization request to the AS over a back-channel before
the browser redirect, eliminating parameter exposure in browser history. JAR (#64) wraps
the parameters in a client-signed JWT, preventing tampering even over the back-channel.
JWT-Bearer client authentication (#65) replaces client_secret with a signed JWT assertion,
making client credentials tamper-evident and eliminating the need to distribute shared
secrets. Together these three features satisfy the FAPI 2.0 Security Profile baseline.
CIBA backchannel authentication (RFC 9509, #66)
Decouples the consumption device from the authentication device. IoT appliances, voice
assistants, call center terminals, and TV apps can now trigger authentication without a
browser redirect. The user approves on their phone. Supports Poll and Ping delivery modes.
Implement the AuthenticationDevice interface to connect any push notification system.
MySQL 8.x storage backend (#62)
Full parity with the postgres adapter. storage/mysql satisfies both theauth.Storage
and OAuthServerStorage. Enable contract testing with THEAUTH_MYSQL_CONTRACT=1. Use
on PlanetScale, AWS RDS MySQL, or any MySQL 8.x host.
Cognito + Auth0 migration CLI (#63)
cmd/theauth-migrate cognito and cmd/theauth-migrate auth0 export user data into an
auditable intermediate JSON bundle, validate it, and apply it to any theauth-go storage
backend. Auth0 users keep their bcrypt password hashes; theauth-go re-hashes with Argon2id
transparently on the next login (controlled by PasswordPolicy.AllowLegacyBcrypt).
Signed releases (goreleaser + cosign + SLSA, #57)
Every release artifact is now signed with Sigstore keyless signing via the GitHub Actions
OIDC identity and comes with a CycloneDX SBOM and SLSA level-3 provenance attestation.
Verify with:
cosign verify-blob \
--certificate-identity "https://github.com/glincker/theauth-go/.github/workflows/release.yml@refs/tags/v2.4.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature theauth-go-v2.4.0.tar.gz.sig \
--certificate theauth-go-v2.4.0.tar.gz.cert \
theauth-go-v2.4.0.tar.gz
gh attestation verify theauth-go-v2.4.0.tar.gz --repo glincker/theauth-goPublic storagetest contract suite (#58)
storagetest.Run(t, func() theauth.Storage { return mystorage.New() })Twelve functional areas. Both in-tree adapters run it in CI.
Upgrading from v2.3
All additions are opt-in via new nil-by-default config fields. Existing code compiles
and runs unchanged. See the v2.3 to v2.4 migration guide
for opt-in instructions.
go get github.com/glincker/theauth-go@v2.4.0
go mod tidy
go build ./...Full changelog
See CHANGELOG.md for
the complete entry, including per-PR change details, RFC citations, and notes on the
storagetest contract coverage.
What is NOT changing
mcpresourcemodule gains zero new transitive dependencies.theauth.StorageandOAuthServerStorageinterfaces are unchanged.- All existing config structs compile unchanged.
- Public API is byte-stable.
v2.3.0 - MCP wedge deepening and enterprise feature parity
The "MCP wedge deepening and enterprise feature parity" release. Three major features close the gap with Auth0, Clerk, and Better-Auth.
Public API is additive with v2.2.0. Root go.mod gains zero new transitive deps; mcpresource untouched.
Highlights
Account linking + identity merge (#55)
Users can bind a new auth method to an existing account, or merge two accounts into one, behind mandatory step-up auth. Closes a gap every major auth library has.
LinkOAuthToCurrentUser/LinkPasswordToCurrentUser/MergeAccounts- HTTP endpoints under
/account/identities(link, callback, password, merge, delete) - New errors:
ErrIdentityConflict,ErrStepUpRequired,ErrLastAuthMethod - New audit events:
identity.linked,identity.unlinked,account.merged - 1,545 LOC, end-to-end tested
+8 OAuth providers, 12 total (#54)
Goth supports 35, Auth0 supports 50, Clerk supports 25. We just doubled.
- Apple (ES256 JWT client secret from .p8 key)
- Facebook, Slack, GitLab (self-hosted via
BaseURL), Bitbucket, Twitch, LinkedIn, X (PKCE-mandatory) - Each ships with its own
examples/oauth-<provider>/minimal demo - 4,127 LOC across 36 files
SIEM audit streaming sinks (#53)
SIEM integration is a procurement requirement. We now ship three sink implementations with a stable AuditSink interface.
audit/sinks/otlp-- OTLP/HTTP logs (own go.mod; root gains zero new deps)audit/sinks/splunkhec-- Splunk HEC envelope + token authaudit/sinks/webhook-- generic CloudEvents 1.0 POST with HMAC-SHA256 signature- Per-sink
WithRedactoroption for stricter PII stripping - Best-effort contract: failed sinks NEVER block storage writes
- New stats field
Stats.AuditSinkFailed
See CHANGELOG.md for the full entry.
v2.2.0 — production-grade observability and audit closure
The "production-grade observability and audit closure" release. Three RFC-level features (CIMD, DPoP, observability adapters), four security closures, four perf items, +175 percentage points of direct handler coverage on two highest-blast-radius packages, and an architectural cleanup that brings theauth.go from 1,171 LOC under the 500 LOC ceiling.
Public API stays byte-stable with v2.1.0. Every addition is additive; downstream code compiles unchanged.
Highlights
Spec features
- CIMD (#42) — OAuth Client ID Metadata Documents per MCP spec 2025-11-25. Clients identify by HTTPS URL; metadata cached with TTL;
DenyAlltrust policy default (fail-closed). - DPoP (#43) — RFC 9449 sender-constrained access tokens. Stolen tokens cannot be replayed without the holder's private key. Wire via
Config.AuthorizationServer.DPoPandmcpresource.WithDPoPVerification(...).mcpresourcegains zero new transitive deps. - Observability adapters (#44) — pluggable
Tracer+Metricsinterfaces with 10 spans + 10 metrics live. Real-dep example bridges to OTel and Prometheus live inexamples/observability-{otel,prom}/with their own go.mod; root andmcpresourcego.mod gain zero new deps.
Security
- Lows L1-L3 + L5 (#45) — magic-link rate limit, PKCE constant-time compare, RevokeToken walks refresh family,
Config.RequireStateknob. - Mediums M3-M5 (#48) —
SecureCookiedeprecation warning ahead of v3.0 default flip, JWKS rotation transactional with partial unique index,mcpresource.Validator.Diagnostics()for misconfiguration warnings. - N1 regression guard —
TestSuspendAgentBustsClientAuthCacheconfirms revoked agents cannot re-authenticate via cached Argon2 entries within the 5-minute TTL.
Performance (#46)
- SCIM auth uses a single storage lookup.
keyedLimiterusessync.RWMutexplus atomiclastUsed(5-10x read-heavy throughput improvement under contention).- 5-second chain-walk cache eliminates 3+ storage calls per 3-deep chain.
- Audit redactor uses precomputed lowercase key set with
strings.EqualFold.
Tests
internal/saml/handlers: 0% to 91.4% statement coverage (#47).internal/webauthn/handlers: 0% to 84.5% (#47).internal/organizations/handlers: 0% to 94.0% (#49).internal/admin/handlers: 0% to 81.1% (#49).
Architecture (#50)
theauth.go: 1,171 LOC to 383 LOC.- Wiring extracted to
wiring.go, storage interface tostorage.go, config sub-structs toconfig.go. - OAuth provider state machine moved to
internal/oauth/service.go. internal/account/handlers/andinternal/admin/handlers/parent packages collapsed.
Fixes
InsertOAuthClientcoerces nil text[] slices to empty arrays (#40), fixingSQLSTATE 23502on fresh schemas.postgres.Migrate(ctx, pool)runtime migration helper (#32) so downstream consumers can delete their forked migration code.
See CHANGELOG.md for the full entry.
v2.1.0: internal package reorg + security/perf/reliability hardening
Internal architecture reorganization plus the v2.0 security audit followups.
The public API is byte-stable with v2.0: every exported type, function,
and method on *theauth.TheAuth keeps the same identifier, signature, and
method set. Downstream consumers compile unchanged. Anyone relying on
unexported symbols via //go:linkname or unsafe reflection may break (we
deleted several dead unexported root methods and consolidated forwarders).
Internal package reorganization (PRs #20 through #28)
The 1.9k-line monolithic root grew to 49 non-test root files at v2.0; PRs
#20 through #28 extracted feature-by-feature implementations into
internal/<flow> subpackages while the root kept the public surface as
thin forwarders. PR G (this release) collapsed the remaining one-line
forwarders into four grouped files and removed the dead bridges that
earlier extractions left behind.
- New internal packages added across the refactor:
internal/models,
internal/as,internal/as/handlers,internal/agent,
internal/agent/handlers,internal/delegation,internal/session,
internal/password,internal/password/handlers,internal/totp,
internal/totp/handlers,internal/webauthn,internal/webauthn/handlers,
internal/magiclink,internal/oauth/handlers,internal/saml,
internal/saml/handlers,internal/scim,internal/scim/handlers,
internal/organizations,internal/organizations/handlers,
internal/rbac,internal/audit,internal/account/handlers,
internal/admin/handlers,internal/clientauthcache,
internal/jwt,internal/chain,internal/httpx,internal/wavt,
internal/ulid,internal/bench,internal/samltest. - Root non-test
.gofile count: 49 (v2.0) to 28 (v2.1). Public surface
unchanged.
Dead code purge (PR G)
PR G deleted seven unused unexported root methods that lost their last
in-tree caller during PRs B through F. Every one was forwarded to a
*as.Service or *<flow>.Service method that handler packages now reach
directly. None were on the public surface; consumers are unaffected.
(*TheAuth).currentSigningKey(was injwks.go)(*TheAuth).publicKeyByKID(was injwks.go)(*TheAuth).invalidateClientAuthCache(was inas.go)(*TheAuth).agentBySubjectClaim(was inservice_agent.go); the
AgentLookupadapter wired intheauth.Newnow points at
a.agentSvc.AgentBySubjectClaimdirectly.(*TheAuth).authenticateClient(was inservice_token.go)(*TheAuth).finishRegistrationFromRequest(was in
service_webauthn.go); every handler now wraps the body in
http.MaxBytesReaderitself.(*TheAuth).finishLoginFromRequest(was inservice_webauthn.go)
Forwarder consolidation (PR G)
Twenty-one service_*.go forwarder files became three grouped files plus
three substantive service files preserved as-is (because they still hold
local logic, not just one-line thunks).
forwarders_identity.go: session, magic-link, password, TOTP,
WebAuthn, audit.forwarders_oauth.go: DCR, introspect, revoke, token, token v3 / v4
grants, AS metadata, protected-resource metadata, authorize.forwarders_enterprise.go: SCIM, organizations, RBAC, delegation.- Retained:
service_oauth.go(OAuth state cache + GC + provider flow),
service_agent.go(validateAgentConfigplus the agent CRUD
forwarders),service_saml.go(toInternaladapter plus SAML
forwarders).
Twelve root handlers_*.go files became eight: the five thinnest mount
forwarders (oauth, password, totp, webauthn, oauth_server) collapsed
into mounts_extracted.go; the six handler files that carry substantive
service adapters (handlers_account.go, handlers_admin.go,
handlers_admin_agents.go, handlers_organizations.go,
handlers_saml.go, handlers_scim.go) plus the top-level handlers.go
stay separate.
errors_v20.go merged into errors.go; models_v20.go merged into
models.go.
Security (audit 2026-06-20, shipped in PR #17)
- H1: POST
/oauth/registerBearer gate now validates the supplied token
againstAuthorizationServerConfig.RegistrationTokensunder
crypto/subtle.ConstantTimeCompareagainst pre-hashed sha256 digests.
The legacy "any non-empty bearer is accepted" behavior is gone. Empty
and unknown bearers return 401 access_denied. WhenRegistrationTokens
is empty andAllowAnonymousRegistrationis false (the default and
production-recommended state), all registration requests are denied. - H2: POST
/oauth/registeris now rate limited per source IP. The cap
defaults to 1 req/min whenAllowAnonymousRegistrationis true (the
documented public-MCP profile) and 5 req/min otherwise. Operators can
override via the new
AuthorizationServerConfig.RegistrationRateLimitPerMinutefield;
negative values disable the cap entirely. - H3: organization-scoped delegation admin (POST
/admin/v1/organizations/{orgID}/delegations) now verifies that
body.userIdis a member of the calling admin's organization before
creating the grant. - H4:
X-Forwarded-Foris no longer trusted by default. The new
Config.TrustedProxies []netip.Prefixfield gates XFF: the header is
consulted only when the incomingr.RemoteAddris inside one of the
configured prefixes. - M2:
AddOrganizationMembernow refuses to demote the last owner of an
organization. - M6: the email + password signin path pays the Argon2id verify cost on
the user-not-found and password-empty branches by verifying against a
fixed dummy PHC hash synthesized once atNewtime.
Reliability (PR #18)
- Closed the handleToken / SCIM PATCH / end-to-end AS to mcpresource
reliability gaps surfaced by the 2026-06 test audit.
Performance (PR #19)
clientauthcacheinterposes between the AS handlers and the Argon2id
client_secret verifier. First verification pays the Argon2id cost; every
subsequent verification of the same(client_id, secret_hash)pair is
served from the cache until the secret rotates or the entry ages out.- JWKS key cache: the AES-decrypted Ed25519 private key is decrypted once
per signing key version (rotation refreshes it) and stashed on
*as.Service. Cold path is unchanged; hot path drops one AES decrypt
per JWT mint. mcpresourcevalidator switched fromsync.Mutextosync.RWMutexon
the JWKS + introspection caches so concurrent reads no longer serialize.
Removed in PR G
- Seven unused unexported methods on
*TheAuth(see "Dead code purge"
above). - Twenty-one
service_*.gofiles merged into threeforwarders_*.go
files (see "Forwarder consolidation"). - Five
handlers_*.gofiles merged into onemounts_extracted.go. errors_v20.gomerged intoerrors.go.models_v20.gomerged intomodels.go.
Deferred from the 2026-06-20 audit
Tracked as follow-up issues: M1 (SCIM cross-tenant email fallback), M3
(SecureCookie default), M4 (JWKS rotation transaction), M5 (mcpresource
missing-introspection startup warning), L1-L5, I1-I7.
[2.0.0] - 2026-06-20
v2.0 ships the OAuth 2.1 Authorization Server, agent identity and
delegation chains (RFC 8693), and the mcpresource validator SDK for MCP
servers. Three alpha tags shipped phases incrementally
(v2.0.0-alpha.1 through v2.0.0-alpha.3); v2.0.0 consolidated them.
Added in v2.0 phase 5 + 6 (targeted for v2.0.0)
- New separately importable Go module:
github.com/glincker/theauth-go/mcpresource.
Zero dependencies outside the standard library: a consumer importing the
package does not transitively pull theauth core or the storage adapters.
Public surface:Validator,Principal,Option,New,
PrincipalFromContext,WithJWKS,WithIntrospection,WithCacheTTL,
WithHTTPClient,WithClockSkew,(*Validator).Middleware,
(*Validator).Principal. Validates JWT signature against a cached JWKS
(refreshes on kid miss and at the configured cache TTL), enforces the
audience claim against the configured resource URI, checks expiry, nbf,
and iat with a 60 second skew tolerance by default, and walks the RFC 8693
actchain via the AS introspection endpoint so revocations propagate
inside the configured cache window. On any failure the middleware emits
HTTP 401 withWWW-Authenticate: Bearer error="invalid_token", resource_metadata="..."per RFC 6750 + RFC 9728. - RFC 9728 OAuth 2.0 Protected Resource Metadata: the AS exposes
GET /.well-known/oauth-protected-resource(bare path returns the first
configured resource) andGET /.well-known/oauth-protected-resource/{path}
(per-resource discovery for multi-resource deployments). The document
carriesresource,authorization_servers,bearer_methods_supported,
plusscopes_supported,resource_name,jwks_uri, and
resource_signing_alg_values_supported. - Organization-scoped admin UX under
/admin/v1/organizations/{orgID}:
GET/POST/PATCH/DELETE /agentsand
GET/POST/DELETE /delegations. Gated by the new seeded permissions
agents:adminanddelegations:admin; both are added to the owner and
admin default org roles. - End-user self-service UX under
/account(enabled via
Config.AccountUX):GET/POST /agents,DELETE /agents/{id},
GET/POST /delegations,POST /delegations/{id}/revoke. Session-cookie
gated; cross-user calls return 404 to avoid leaking record existence. - New seeded RBAC permissions:
agents:admin,delegations:admin.
Existing consumers see the additional permissions on the next
SeedPermissionscall; downstream role definitions that did not pre-grant
them stay valid (catalog only grows). - New error sentinels:
ErrAccountUXRequiresAgents. - Example
examples/mcp-server/: standalone runnable demo of the
mcpresource middleware on a tiny chi server. Shows the one-import claim:
middleware wiring plus principal extraction in roughly ten lines of Go. - Audit emission additions: every admin and account mutation emits the same
events used by th...