ubgo is in early alpha: libraries and APIs may still change.

Skip to content
ubgo Alpha

auth

Embeddable authentication with plugins for email, password, phone, OAuth, passkeys, 2FA, API keys, JWT and organizations, stored in your own Postgres, MySQL or SQLite.

go get github.com/ubgo/auth

Each plugin and adapter is its own module, tagged v0.1.0 with the core. Add the ones you use, for example: go get github.com/ubgo/auth/plugin/password

Language
Go
Go version
1.24.0
Core deps
1 direct
Latest tag
v0.1.0
License
Apache-2.0
Go modules
29

How it fits together

Highlights from the README

  • Only what you use. An app imports only the sign-in methods it offers, and a plugin of your own gets the same powers as the built-in ones: operations, hooks, columns on the user table, its own tables and migrations.
  • Your database, your joins. The auth tables sit beside yours. Read and join them with typed sqlb columns, so a renamed column is a compile error rather than a production bug. Only writes are closed, because the invariants live in Go.
  • One pipeline for every entry point. Each feature is one typed operation. A Go call, an HTTP route and a GraphQL resolver all run the same code, so none can skip the rate limit, the CSRF origin check, the session requirement or a hook.
  • Security pinned by tests. Constant-time comparison of every secret is a source guard that fails the build; one password hash on every sign-in path, known account or not, is a timing test; only verified identities ever match a person; tokens are single-use under concurrency.
  • Three engines, one behaviour. Values are normalized in Go and compared byte for byte, and one integration suite runs on Postgres, MySQL and SQLite.
  • A dependency-free core. The core module depends on the standard library and sqlb only, enforced by a test; argon2, WebAuthn and each framework adapter are separate modules you download only if you import them.

Code from the README

a, err := auth.New(
	auth.WithDB(db, sqlb.PostgresDialect{}),
	auth.WithBaseURL("https://app.example.com"),
	auth.WithPlugins(
		email.New(email.Login(), email.Recover(), email.WithMailer(mailer)),
		password.New(),
	),
)
if err != nil {
	log.Fatal(err) // a broken plugin combination fails here, not in production
}

mux.Handle("/auth/", authhttp.Handler(a, authhttp.Prefix("/auth")))
mux.Handle("/me", authhttp.Middleware(a)(authhttp.RequireUser(meHandler)))

From the auth README.

At a glance (from the README)

PackageWhat it adds
plugin/emailEmail identities, verification links, change with undo, many addresses per user, sign-in codes
plugin/passwordargon2id passwords, sign-up, sign-in, reset, change; password/hibp checks breaches; a separate module
plugin/phonePhone identities, SMS codes, sign-in and sign-up with a code alone
plugin/usernameHandles, reserved names, availability
plugin/ldapSign in with the username and password of an LDAP directory (OpenLDAP, Active Directory, FreeIPA, lldap), checked by the directory and never stored; TLS required, a replaceable Directory; a separate module
plugin/siweSign-In with Ethereum (ERC-4361): a single-use nonce, the message checked against your site, chain and time window, several wallets per user, a replaceable Verifier for smart-contract wallets; a separate module
plugin/oauthGoogle, GitHub, any OpenID Connect issuer; optional sealed provider tokens, refreshed before expiry
plugin/twofactorTOTP, mailed or texted codes and security keys, several per user, with recovery codes; asked after password sign-ins by default (better-auth’s rule), any other rule through WithPolicy; RequireEnrolment makes a factor mandatory through a session task
plugin/passkeyPasskeys (WebAuthn); a separate module
plugin/emailchallengePasswordless sign-in by one challenge a link and a code both complete, always signing in the browser that asked
plugin/magiclinkSign-in by a single-use link to a verified address or number
plugin/apikeyAPI keys with scopes for scripts and integrations
plugin/jwtShort-lived JWTs (EdDSA, RS256 or ES256) from a session, a JWKS, a Go verifier, and named templates shaped for another service (presets for Supabase, Hasura, Convex and Firebase in jwtpreset)
plugin/adminFind and search users, ban and unban, revoke sessions, roles and permissions decided by a pluggable Authorizer (built-in admin and user roles; plugin/access roles, Casbin, OpenFGA or your own), impersonate (visible to the user, one hour at most)
plugin/inviteInvitation codes with usage tracking, optionally invite-only sign-up
plugin/referralUser-to-user referral codes and the steps that complete a referral; a separate module
plugin/termsRecord which terms version each user accepted; block until the current one is
plugin/anonymousGuest sessions before sign-up, linked to the account by an app hook on every sign-in path
plugin/lastloginRemember how and when each user last signed in
plugin/metadataPublic, private and user-editable JSON metadata per user in a table of its own; admin routes decided by your authorizer
plugin/sessiondataPer-session app data in a table of its own, removed with the session; the core carries the same data inline on Session.Data
plugin/handoffHand a session to a native app or another client with a three-minute single-use token
plugin/captchaTurnstile, reCAPTCHA or hCaptcha before sign-up, sign-in and reset, or only when a rate rule asks for a challenge; fails closed
plugin/powProof of work instead of a captcha: a rate or geo rule with the challenge action asks the client for a hashcash proof (X-Pow-Proof), with no third party
plugin/staleMail the owners of accounts nobody has signed in to for a year (configurable) and, if you ask for it, delete the ones that never answer; run from your scheduler with auth maintain
plugin/organizationOrganizations, teams, members, invitations by email or any identifier kind matched only to verified addresses, roles decided by a pluggable Authorizer, an active organization per session, built-in, static and per-organization roles checked by permission, lifecycle hooks, verified email domains that invite, suggest or auto-join; orgjwt puts the active organization in a JWT; a separate module
plugin/accessPermission statements and the roles built from them (better-auth’s access), used by the organization plugin, by the admin plugin through accessadmin, by API-key permissions through accessapikey, and by any app; a separate module
plugin/auditAn audit log of security events, filterable by type and organization, with each user’s own history; any sink (a SIEM, a log pipeline) through Sink and Querier, and any operation mapped to an event type
plugin/ssoEnterprise single sign-on: OpenID Connect and SAML 2.0 identity providers registered at run time per organization or email domain; a separate module
plugin/samlidpYour app as a SAML 2.0 identity provider for wikis, ticketing and vendor tools: metadata, SP-initiated and opt-in IdP-initiated sign-on, signed assertions; a separate module
plugin/scimA SCIM 2.0 server, so Okta, Entra ID and other IdPs provision and deprovision users and groups; a separate module
plugin/deviceauthThe device authorization grant (RFC 8628) for CLIs and TVs; oauthdevice issues OAuth tokens through oauthprovider instead; separate modules
plugin/oauthproxyProvider sign-in on preview deployments through production’s callback; oauthpopup signs in from a popup for apps embedded on other sites; a separate module
plugin/oauthproviderYour app as an OAuth 2.1 authorization server and OpenID Connect provider, for other apps and MCP servers; a separate module
plugin/webhookSigned outgoing webhooks (Standard Webhooks) for user, session, identity and security events, with retries, dead letters, replay and an SSRF guard; webhook/signature verifies them; a separate module
plugin/agentauthAI agents sign in with their own keys and act for a user under the capabilities the user granted (Agent Auth Protocol); a separate module