Documentation
¶
Overview ¶
Package authhttp serves auth operations over net/http.
It is the reference transport and uses only the standard library, so it lives in the core module. Everything it does is available to any other transport through the same public API — Auth.Operations, Auth.WithRequest, Operation.Invoke, RequestEffects and ErrorOf — and it has no private path into the core. A router that accepts an http.Handler (chi, gorilla/mux, the standard mux) mounts it directly; gin and echo adapters reuse its request and effect handling.
Index ¶
- Constants
- Variables
- func ApplyEffects(w http.ResponseWriter, r *http.Request, effects []auth.Effect, opts ...Option) (redirected bool)
- func ApplyHeaderEffects(w http.ResponseWriter, r *http.Request, opts ...Option) int
- func BearerMode(r *http.Request) bool
- func Begin(a *auth.Auth, r *http.Request, opts ...Option) (*http.Request, error)
- func CurrentUser(r *http.Request) (auth.User, bool)
- func ForgetRejectedToken(w http.ResponseWriter, r *http.Request)
- func HTTPRequest(ctx context.Context) (*http.Request, bool)
- func Handler(a *auth.Auth, opts ...Option) http.Handler
- func HasCredential(r *http.Request, opts ...Option) bool
- func LocaleOf(header string) string
- func MethodOf(kind auth.OpKind) string
- func Middleware(a *auth.Auth, opts ...Option) func(http.Handler) http.Handler
- func MustUser(r *http.Request) auth.User
- func OpenAPIDocument(a *auth.Auth, info OpenAPIInfo, opts ...Option) []byte
- func OperationHandler(a *auth.Auth, op auth.Operation, opts ...Option) http.Handler
- func RequestOf(r *http.Request, opts ...Option) auth.Request
- func RequireFunc(check func(r *http.Request) error) func(http.Handler) http.Handler
- func RequireScope(scope string) func(http.Handler) http.Handler
- func RequireUser(next http.Handler) http.Handler
- func RequireUserExcept(next http.Handler, allow ...string) http.Handler
- func SetRetryAfter(w http.ResponseWriter, err error)
- func TypeScriptClient(a *auth.Auth) (map[string][]byte, error)
- func TypeScriptErrorCodes() []byte
- func WrapWriter(w http.ResponseWriter, r *http.Request, opts ...Option) (http.ResponseWriter, func() (late int))
- func WriteError(w http.ResponseWriter, err error)
- func WriteErrorFor(w http.ResponseWriter, r *http.Request, err error)
- func WriteErrorWith(w http.ResponseWriter, r *http.Request, err error, opts ...Option)
- type CookieAttributes
- type CookieRole
- type ErrorEncoder
- type ErrorPageData
- type ErrorPageOptions
- type HTTPMiddlewarer
- type OpenAPIInfo
- type Option
- func APIKeyFrom(get func(r *http.Request) string) Option
- func APIKeyHeader(name string) Option
- func AcceptForms() Option
- func ClientIPFunc(fn func(r *http.Request) string) Option
- func ClientIPHeaders(names ...string) Option
- func CookieDomain(domain string) Option
- func CookieDomainFromBaseURL() Option
- func CookiePrefix(prefix string) Option
- func EncodeErrors(enc ErrorEncoder) Option
- func ErrorPage(opts ErrorPageOptions) Option
- func Health() Option
- func LocaleCookie(name string) Option
- func LocaleFunc(fn func(r *http.Request) string) Option
- func Logger(l *slog.Logger) Option
- func OpenAPI(info OpenAPIInfo) Option
- func PartitionedCookies() Option
- func Prefix(p string) Option
- func Reference(opts ReferenceOptions) Option
- func RenameCookie(role CookieRole, name string) Option
- func SameSiteStrict() Option
- func SkipTrailingSlash() Option
- func TrustedProxies(networks ...string) Option
- func WeakenCookie(role CookieRole, a CookieAttributes) Option
- func WeakenCookies(a CookieAttributes) Option
- type PluginMiddleware
- type ReferenceOptions
- type ReferenceTheme
- type SignInMethod
- type TSMethod
- type UIMethod
Constants ¶
const ( //ds:def id=domaincookiename-y5xjae8s owner=@khanakia stability=stable DomainCookieName = "__Secure-auth_session" //ds:def id=domainflowcookiename-pgbydmxs owner=@khanakia stability=stable DomainFlowCookieName = "__Secure-auth_flow" DomainCacheCookieName = "__Secure-auth_session_cache" //ds:def id=domaindevicecookiename-3hn7kp9r owner=@khanakia stability=stable DomainDeviceCookieName = "__Secure-auth_device" DomainAccountCookiePrefix = "__Secure-auth_account_" )
Cookie names when CookieDomain is set. __Host- forbids a Domain attribute, so a cookie shared with subdomains uses the __Secure- prefix, which still requires HTTPS.
const ( //ds:def id=tokenmodeheader-s6xp5wjc owner=@khanakia stability=stable TokenModeHeader = "Auth-Token-Mode" //ds:def id=tokenmodebearer-3p2r26xe owner=@khanakia stability=stable TokenModeBearer = "bearer" //ds:def id=settokenheader-j7vbxqvy owner=@khanakia stability=stable SetTokenHeader = "Set-Auth-Token" //ds:def id=settokenexpiresheader-rxwtm4ay owner=@khanakia stability=stable SetTokenExpiresHeader = "Set-Auth-Token-Expires" // SessionCacheHeader carries, in bearer mode, the client's copy of its // session from a snapshot SessionCache; SetSessionCacheHeader hands it // out. They are the bearer-mode twins of CacheCookieName. SessionCacheHeader = "Auth-Session-Cache" SetSessionCacheHeader = "Set-Auth-Session-Cache" // DeviceHeader carries, in bearer mode, the device token of // DeviceCookieName; SetDeviceHeader hands it out, and an empty value // there means the client must forget it. //ds:def id=deviceheader-7rt2wq5m owner=@khanakia stability=stable DeviceHeader = "Auth-Device" //ds:def id=setdeviceheader-4kx8mv3p owner=@khanakia stability=stable SetDeviceHeader = "Set-Auth-Device" )
Bearer mode. A client that cannot keep cookies — a mobile app, a CLI — sends TokenModeHeader: bearer on sign-in and then Authorization: Bearer on every request. An issued session then arrives in SetTokenHeader instead of a cookie, and never in the JSON body, so a cookie client's page scripts can never read a token out of a response. SetTokenHeader with an empty value, on any status, means the client must forget its token: the session ended (EffectClearSession) or the token sent names no session (ForgetRejectedToken).
const ( ErrorPageTitleCode = "authhttp.error_page.title" ErrorPageHeadingCode = "authhttp.error_page.heading" ErrorPageBackCode = "authhttp.error_page.back" )
Message codes of the built-in error page's fixed strings. They are not error codes: an app translates them in the same auth.WithMessages catalogue as its errors ({"de": {"authhttp.error_page.heading": "..."}}), and the page falls back to the English below for a locale that lacks one.
const ( // TSFileClient is the generated part: one TypeScript type per Go input // and output type, one operation table and plugin factory per plugin, // and createAuthClient typed with the core's operations. TSFileClient = "client.ts" // TSFileRuntime is the hand-written fetch runtime, the same for every // app: request encoding, token modes, error decoding. TSFileRuntime = "runtime.ts" // TSFileErrorCodes is every error code the linked packages declare // (auth.Errors), the same module docs/reference/error-codes.ts is. TSFileErrorCodes = "error-codes.ts" // TSFileExtensions is the hand-written plugins that are not one // request each, the same for every app: oauthPopup() (the popup // sign-in of plugin/oauthproxy/oauthpopup) and lastLoginMethod(). TSFileExtensions = "extensions.ts" // TSFileIndex re-exports the four, the module an app imports. TSFileIndex = "index.ts" )
The files TypeScriptClient writes, by name. They import each other by these names, so a client is the five files in one directory.
const AccountCookiePrefix = "__Host-auth_account_"
AccountCookiePrefix starts the name of each cookie holding one of the device's OTHER signed-in accounts under auth.WithMultiSession, one cookie per session token so a single account can be dropped without rewriting the rest. The rest of the name is the first accountCookieHashLen hex digits of the token's SHA-256, which identifies the cookie to delete without the name itself being usable as a credential. The active account stays in CookieName.
const CacheCookieName = "__Host-auth_session_cache"
CacheCookieName holds the client's copy of its session from a snapshot SessionCache (auth.EffectCacheSession), so the next requests skip the session lookup. It is hardened like the session cookie: the value is sealed and tied to the session token, but it is still the user's record.
const CookieName = "__Host-auth_session"
CookieName is the session cookie. The __Host- prefix makes the browser refuse the cookie unless it is Secure, has Path=/ and has no Domain, so no subdomain can set or read it.
const DefaultCookiePrefix = "auth"
DefaultCookiePrefix is the app's part of every cookie name when CookiePrefix is not given: "auth" in "__Host-auth_session".
const DefaultLocaleCookieName = "auth_locale"
DefaultLocaleCookieName is the cookie LocaleCookie reads when given no name. It is not a __Host- cookie: a language choice is not a secret and an app's own page sets it from JavaScript.
const DefaultReferenceScript = "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.4/dist/browser/standalone.js"
DefaultReferenceScript is the viewer the reference page loads when ReferenceOptions.ScriptURL is empty: Scalar's standalone bundle from jsDelivr, pinned to one release so the page does not change under an app that never asked it to. An app that will not load scripts from a third party serves the bundle itself and names its URL.
const DeviceCookieName = "__Host-auth_device"
DeviceCookieName holds the device token a second-factor plugin hands out with auth.EffectRememberDevice ("remember this browser"), presented back as auth.Request.Device. It is hardened like the session cookie and, unlike it, survives sign-out: it exists to skip the second factor on the NEXT sign-in, so clearing it with the session would defeat it.
const ErrorPagePath = "/error"
ErrorPagePath is the route ErrorPage adds under the prefix, better-auth's /api/auth/error: the page a failed browser flow lands on when it is given as auth.WithErrorURL (prefix + ErrorPagePath).
const FlowCookieName = "__Host-auth_flow"
FlowCookieName holds the binding of a multi-step flow such as an OAuth sign-in; see auth.EffectBindFlow.
const ( // HeaderForwarded is the standard header (RFC 7239) a proxy writes the // client's address into, as for= parameters. It is not read by default; // name it in ClientIPHeaders to read it from a trusted proxy. HeaderForwarded = "Forwarded" )
Headers a reverse proxy writes the client's address into. X-Forwarded-For is a list, nearest proxy last; X-Real-IP is one address.
const HealthPath = "/ok"
HealthPath is the route Health adds under the prefix. A load balancer or a deploy script calls it to learn that the auth routes are mounted and the process answers; it touches no database, so it says nothing about one.
const OpenAPIPath = "/openapi.json"
OpenAPIPath is the route OpenAPI adds under the prefix. The document is built once, when Handler is built, from the same operation list the routes come from, so it can never describe a route that is not served.
const OpenAPIVersion = "3.1.0"
OpenAPIVersion is the specification version the document declares. 3.1 is the first whose schemas are plain JSON Schema, which is what a nullable pointer field needs (`"type": ["string", "null"]`).
const ProtocolErrorMember = "auth_error"
ProtocolErrorMember is where a handler that answers a standard's own error body on an operation's route also puts the library's error ({code, message}), because the standard already owns "error": RFC 6749 §5.2's {"error": "authorization_pending"}, which deviceauth's CodeHandler and TokenHandler answer. A client of the standard ignores the extra member; the generated TypeScript client reads it, so it reports the plugin's declared code rather than an unreadable body. A handler that answers the library's own body (every handler in this package) never needs it.
const ReferencePath = "/reference"
ReferencePath is the route Reference adds under the prefix: a page that renders the OpenAPI document as an interactive API reference.
const WarnCookiesWeakened = "" /* 145-byte string literal not displayed */
WarnCookiesWeakened is logged by Handler and Middleware while WeakenCookies or WeakenCookie turns a protection off.
Variables ¶
var CookieRoleValues = []CookieRole{CookieSession, CookieFlow, CookieCache, CookieDevice}
CookieRoleValues is every CookieRole.
var ErrMethodNotAllowed = auth.NewError("auth.method_not_allowed", http.StatusMethodNotAllowed, "This method is not allowed here.")
ErrMethodNotAllowed answers a known path called with the wrong method.
var ErrMissingScope = auth.NewError("auth.missing_scope", http.StatusForbidden, "This key does not allow this.")
ErrMissingScope answers a request that has a principal, a key or a session, without the scope a route requires.
var ErrNoUser = errors.New("authhttp: MustUser called on a request without a user; wrap the handler in RequireUser")
ErrNoUser is the panic value of MustUser outside RequireUser.
var ErrTypeScriptName = errors.New("authhttp: TypeScript name collision")
ErrTypeScriptName is returned by TypeScriptClient when two things would get one TypeScript name: two operations of a plugin whose ids differ only in punctuation, two Go types whose package and name concatenate alike, a plugin named after a TypeScript keyword or a runtime export. The message names both, so the plugin can be renamed.
var ReferenceThemeValues = []ReferenceTheme{ ThemeDefault, ThemeAlternate, ThemeMoon, ThemePurple, ThemeSolarized, ThemeBluePlanet, ThemeSaturn, ThemeKepler, ThemeMars, ThemeDeepSpace, ThemeLaserwave, ThemeNone, }
ReferenceThemeValues is every ReferenceTheme, for validation and docs.
Functions ¶
func ApplyEffects ¶
func ApplyEffects(w http.ResponseWriter, r *http.Request, effects []auth.Effect, opts ...Option) (redirected bool)
ApplyEffects writes the effects of a request as HTTP headers and reports whether one of them was a redirect, in which case the response is complete. It must run before the body is written.
func ApplyHeaderEffects ¶
ApplyHeaderEffects writes r's effects that can be headers — every kind but a redirect — and returns how many effects it saw. An adapter whose framework has its own writer type calls it just before the first write.
func BearerMode ¶
BearerMode reports whether r asked for sessions as bearer tokens.
func Begin ¶
Begin attaches r to the core and resolves its session, returning the request with the new context. Every net/http-based adapter starts a request with it, so none of them builds the auth request differently.
func CurrentUser ¶
CurrentUser returns the signed-in user of r. It is auth.CurrentUser for code that holds a request rather than a context.
func ForgetRejectedToken ¶
func ForgetRejectedToken(w http.ResponseWriter, r *http.Request)
ForgetRejectedToken tells a bearer client to forget its token when the request presented one that names no session (auth.RequestTokenRejected: unknown, expired, revoked, or older than the user's SessionsValidAfter), by sending SetTokenHeader with an empty value. A pending second-factor token is alive and is never cleared, which is why a client cannot decide this from an auth.signed_out answer alone. It leaves a SetTokenHeader already on w alone, so a session issued in the same response is never overwritten, and it does nothing in cookie mode, where a dead cookie costs nothing more than the 401. Every error and effect writer of this package calls it; an adapter that writes its own error body calls it before the first write. A nil r (Middleware answering a lookup that failed before a request existed) is no request to judge, and does nothing.
func HTTPRequest ¶
HTTPRequest returns the *http.Request an operation, a hook or a handler behind Middleware is running for, when the call came through this package or an adapter built on it (Begin): better-auth's ctx.request, and with it ctx.query (r.URL.Query()). False for a direct Go call, which has no request.
It is the raw request on purpose, for what the typed input does not carry (a header the app's own proxy sets, a query parameter a hook keys on). Treat everything in it as client input, and do not read its body: the operation's decoder has already consumed it. c.Header and c.Client stay the transport-neutral way to read a header and the client's address.
func Handler ¶
Handler serves every registered operation: a Query as GET, a Mutation as POST, at prefix + the operation's route.
Routing is an exact-path table rather than http.ServeMux, because every route is a fixed path and the answers for a miss must be the library's own JSON errors: 404 for an unknown path, 405 with an Allow header for a known path and the wrong method. ServeMux cannot give both once a catch-all pattern exists.
func HasCredential ¶
HasCredential reports whether r carries a session cookie or a bearer token at all, without looking it up. It is for a cheap outer layer, such as a redirect to the sign-in page before a page renders, where a database call per request is not wanted. Presence is not validity: a forged or expired cookie passes here, so every protected action still runs behind Middleware and RequireUser, which do the lookup.
func LocaleOf ¶
LocaleOf returns the first language tag of an Accept-Language header, or "" when it is missing or not a plausible tag. The value comes from the client, so it is reduced to letters, digits and "-" before anything uses it.
func MethodOf ¶
MethodOf is the HTTP method an operation of kind is served with. Exported so every net/http-based adapter maps kinds the same way.
func Middleware ¶
Middleware attaches the request to the context and resolves the session, for the app's own handlers and for a GraphQL handler. Effects recorded by operations called inside the handler, such as a gqlgen sign-in mutation, are written as headers before the handler's first byte.
A session lookup that fails for a reason other than "signed out" — the database is down — answers 500 here rather than letting the handler run as if the user were signed out. A coded refusal from a token plugin (an API key that is disabled, expired or over its rate limit) answers its own code and status, as the gin, echo and fiber adapters do.
func MustUser ¶
MustUser returns the signed-in user of r and panics without one. It is for handlers behind RequireUser, where a missing user is a wiring bug.
func OpenAPIDocument ¶
func OpenAPIDocument(a *auth.Auth, info OpenAPIInfo, opts ...Option) []byte
OpenAPIDocument is the document OpenAPI serves, for a generator that writes it to a file and checks the committed copy (task docs:api). It takes the same options as Handler so the prefix, AcceptForms and Health in the document match the routes. It cannot fail: the document is nested maps of strings, numbers and booleans, which encoding/json always encodes.
func OperationHandler ¶
OperationHandler serves one operation, for a router that registers each route itself, as the gin and echo adapters do. It answers the same way as Handler: same decoding, same effects, same error JSON.
func RequestOf ¶
RequestOf describes an http.Request to the core. Exported so gin, echo and any other net/http-based adapter build exactly the same Request.
The cookie wins over a bearer header when both are present. The client IP is the connection's remote address unless TrustedProxies names the network the request came from, because trusting X-Forwarded-For by default would let any client pick its own IP and escape per-IP rate limits.
func RequireFunc ¶
RequireFunc refuses a request that check rejects, answering with check's error as JSON: an *auth.Error keeps its code and status, anything else is a 500 with no detail. It is the one place an app's own authorization rule — a role, the ownership of a resource — becomes middleware, so every such rule answers errors the same way. It must be inside Middleware.
func RequireScope ¶
RequireScope refuses a request unless it carries an API key with scope. A request with no principal at all (no credential, or a key that is revoked, expired or unknown) answers 401 auth.signed_out, so a client can tell "authenticate" from "you may not": a script whose key was revoked must get a new key, not ask for more scopes. A principal without the scope answers 403 auth.missing_scope, and that includes a browser session, because a route guarded by a scope is a route for keys. For a route both people and keys use, guard it with RequireUser and decide per request with auth.HasScope. It must be inside Middleware.
func RequireUser ¶
RequireUser refuses a request without a session with 401 and the core's signed-out error, and one whose session has tasks pending with 403 auth.session_task_pending. It must be inside Middleware.
func RequireUserExcept ¶
RequireUserExcept is RequireUser for a whole handler tree with a list of paths that stay open: deny by default, allow by name, so a new page is protected until someone decides otherwise. An entry ending in "/" opens that subtree ("/auth/" opens every auth route); any other entry opens exactly that path ("/login"). It must be inside Middleware.
func SetRetryAfter ¶
func SetRetryAfter(w http.ResponseWriter, err error)
SetRetryAfter sets the Retry-After header, in whole seconds, when err says when to retry (auth.RetryAfter), and does nothing otherwise. It must run before the status is written. It is exported for a transport that answers errors in its own format, such as authui's HTML pages, so every transport tells a limited client the same thing.
func TypeScriptClient ¶
TypeScriptClient writes a typed TypeScript client for every operation a serves, from the same operation list Handler and OpenAPIDocument read, so the client can never call a route that is not served or miss one that is. The answer maps a file name (TSFileClient, TSFileRuntime, TSFileErrorCodes, TSFileExtensions, TSFileIndex) to its contents; the five go in one directory.
Each plugin becomes a factory (password(), twofactor()) whose operation table carries the input and output types; createAuthClient({plugins}) types the client as the core's methods plus one namespace per plugin it was given, so calling a plugin that was not enabled is a compile error. Paths are the routes without a mount prefix: the client's baseURL carries it ("https://app.example.com/auth"), so one client serves any mount.
The types follow what encoding/json writes and the decoders read: a field without omitempty is required, a pointer, slice or map may be null, a `,string` field is a string, and a type with its own MarshalJSON is unknown. Run against an app's own *auth.Auth (authcli's `auth client ts`), it types that app's custom plugins and their error codes exactly.
func TypeScriptErrorCodes ¶
func TypeScriptErrorCodes() []byte
TypeScriptErrorCodes is TSFileErrorCodes: a TypeScript module with every error code the linked packages declare (auth.Errors), sorted, as a const object of status, message and owning plugin, the union type of the codes, the wire shape of an error answer and a guard that narrows a string to a known code. The plugin is the code's prefix, or the core's name for the core's own codes (prefix "auth" or none). apiref writes the same bytes to docs/reference/error-codes.ts.
func WrapWriter ¶
func WrapWriter(w http.ResponseWriter, r *http.Request, opts ...Option) (http.ResponseWriter, func() (late int))
WrapWriter returns a writer that applies r's effects as headers just before the first header or byte, and a finish function to call when the handler returns: it applies the effects of a handler that wrote nothing, and reports how many effects were recorded too late to become headers.
func WriteError ¶
func WriteError(w http.ResponseWriter, err error)
WriteError answers err as the library's error JSON, with its status and, for a rate-limited error, its Retry-After header. An error that is not an *auth.Error answers 500 with no detail, so an adapter cannot leak one by accident. It holds no request, so it never applies EncodeErrors: an adapter that has the request calls WriteErrorFor or WriteErrorWith.
func WriteErrorFor ¶
func WriteErrorFor(w http.ResponseWriter, r *http.Request, err error)
WriteErrorFor is WriteError with the message in the request's locale, when the app configured a catalogue (auth.WithMessages) and r went through Begin, Middleware or Handler so its context knows the Auth. Code and status are the same as WriteError's; without a catalogue so is the message. Every error the handlers and middleware of this package answer goes through it.
With EncodeErrors set on the Handler, Middleware or Begin r went through, the body is the encoder's, under the same status and Retry-After.
func WriteErrorWith ¶
WriteErrorWith is WriteError for an adapter that holds the request and the options but no context from Begin yet, as when Begin itself failed: the body is the EncodeErrors encoder's when opts (or r's context) set one, else the library's JSON. The message is not translated, as WriteError's is not, because a request whose session lookup failed was never attached to the core.
Types ¶
type CookieAttributes ¶
type CookieAttributes struct {
// ScriptReadable drops HttpOnly: page script can read the cookie, so
// one cross-site scripting bug hands the session to an attacker.
ScriptReadable bool
// Insecure drops Secure, and with it the __Host- or __Secure- prefix
// (a browser refuses either without Secure), for plain-HTTP development
// on a non-localhost host. The cookie then crosses the network in clear
// text, and any subdomain or a network attacker can set it.
Insecure bool
// CrossSite sends the cookie with SameSite=None, so it travels on
// requests from other sites, for an app embedded in a third-party page.
// The origin check is then the only CSRF defence. It needs Secure, so
// it cannot be combined with Insecure.
CrossSite bool
// Path narrows the cookie to a path other than "/". __Host- requires
// Path=/, so the cookie is named with __Secure- instead, which a
// sibling subdomain can set.
Path string
}
CookieAttributes turns protections of a cookie off: better-auth's advanced.cookies.<cookie>.attributes and advanced.defaultCookieAttributes (with useSecureCookies). Every field removes one guarantee, so the zero value changes nothing, and Handler and Middleware log WarnCookiesWeakened at start-up while any is set.
type CookieRole ¶
type CookieRole string
CookieRole names one of the cookies this package sets, for RenameCookie.
const ( CookieSession CookieRole = suffixSession CookieFlow CookieRole = suffixFlow CookieCache CookieRole = suffixCache CookieDevice CookieRole = suffixDevice )
The cookies RenameCookie can rename. Their values are the role suffixes the default names end with. Account and plugin cookies keep the prefix rule, since each is a family of names rather than one.
type ErrorEncoder ¶
ErrorEncoder writes an error response in an app's own shape, such as RFC 9457 problem+json or a house envelope, in place of {"error": info}. The library decides everything but the body: info is what auth.ErrorOf (or its translated form) made of the error, so a non-*auth.Error is already the 500 with no detail, and info.Status is the status the client gets.
The encoder cannot get the status wrong: whatever it passes to WriteHeader, the response carries info.Status, and an encoder that writes nothing still answers it with an empty body. Retry-After is set again after the encoder returns, so a limited client is told when to retry even if the encoder removed it. The encoder sets its own Content-Type.
func ErrorEncoderOf ¶
func ErrorEncoderOf(ctx context.Context, opts ...Option) ErrorEncoder
ErrorEncoderOf is the EncodeErrors encoder opts set, else the one ctx carries from Begin, Middleware or Handler, else nil. An adapter whose framework cannot hand out an http.ResponseWriter (fiber) asks it to decide whether to record the encoder's answer or write the library's JSON itself.
type ErrorPageData ¶
type ErrorPageData struct {
Code string
Message string
Description string
// Lang is the page's language tag: the request's locale when the
// catalogue translates the page's heading for it, else "en", so the
// lang attribute always names the language the fixed strings are in.
Lang string
// Title, Heading and Back are the page's fixed strings, from
// auth.WithMessages under ErrorPageTitleCode, ErrorPageHeadingCode and
// ErrorPageBackCode, else English.
Title, Heading, Back string
// Nonce is ErrorPageOptions.Nonce's value for this request, or empty.
Nonce string
}
ErrorPageData is what the page template renders. Message is the code's declared message in the request's locale (auth.WithMessages) when the code is one the library declares, else empty. Description is the provider's error_description, untrusted text the template escapes like any other.
type ErrorPageOptions ¶
type ErrorPageOptions struct {
// Template replaces the built-in page, as better-auth's
// customizeDefaultErrorPage restyles its own: any html/template that
// renders ErrorPageData. html/template escapes every field, so a
// template cannot be tricked into running a link's text.
Template *template.Template
// Redirect sends the request on to the app's own page instead of
// rendering one, carrying the error and error_description parameters,
// as better-auth's onAPIError.errorURL does at /api/auth/error. It must
// be a same-site path or a URL on a trusted origin
// (auth.TrustsReturnURL); Handler panics otherwise, at startup.
Redirect string
// Nonce returns a per-request CSP nonce for the built-in page's style
// element, as ReferenceOptions.Nonce does for its scripts.
Nonce func(*http.Request) string
}
ErrorPageOptions configures ErrorPage. Every field is optional.
type HTTPMiddlewarer ¶
type HTTPMiddlewarer interface {
auth.Plugin
HTTPMiddleware() []PluginMiddleware
}
HTTPMiddlewarer is a plugin that adds net/http middleware to Handler. Handler applies every plugin's middleware in plugin order, the first plugin's outermost, and only to its own routes: the app's handlers are the app's to wrap. Each run is traced as an auth.SpanMiddleware span when auth.WithTracer is set.
It is a transport interface on purpose: an operation called from Go or GraphQL never passes through it, so a rule that must hold on every entry point belongs in an auth.OperationHook instead.
type OpenAPIInfo ¶
type OpenAPIInfo struct {
// Title and Version are required by the specification; empty values are
// written as empty strings rather than invented.
Title string
Version string
Description string
// Servers are the base URLs the routes are reachable at, without the
// prefix (the paths carry it). Empty means the document names none, and
// a client uses the URL it fetched the document from.
Servers []string
}
OpenAPIInfo is the part of the document the library cannot know: what the app is called, which version this is, and where it is served.
type Option ¶
type Option func(*config)
Option configures Handler and Middleware.
func APIKeyFrom ¶
APIKeyFrom reads an API key from wherever the app's clients send it (a query parameter, a signed header pair), as better-auth's customAPIKeyGetter does; "" means the request has none. It runs only when no session cookie, no Authorization header and no APIKeyHeader value is present, and its value is handed to the core as a bearer credential exactly like those, so every key rule still applies. A key in a URL ends up in access logs and browser history; prefer a header. Off by default.
func APIKeyHeader ¶
APIKeyHeader lets an API key arrive in the named header, such as "X-Api-Key", as well as in "Authorization: Bearer". The value is handed to the core as a bearer credential exactly like the Authorization form, so every rule that applies to a key there (no cookie, no origin check, scopes) applies here. A session cookie and an Authorization header both win over it. Off by default: an existing deployment accepts nothing new.
func AcceptForms ¶
func AcceptForms() Option
AcceptForms lets a Mutation take an application/x-www-form-urlencoded body as well as JSON, one value per field, so a plain HTML form can post to a route and work without JavaScript. Nested inputs (lists, objects) are not expressible in a form and stay JSON-only. Off by default: a form body is then a bad request, as before.
func ClientIPFunc ¶
ClientIPFunc replaces how the client's address is found, for a platform whose proxy rules TrustedProxies cannot express: the address arrives in a request context value, a signed header, or a list the app refreshes. fn owns the trust decision completely, so it must never return a value a client controls unless it has checked where the request came from. A value that is not an IP address ("" included) falls back to the connection's address and the TrustedProxies rules, so fn can decline a request and text cannot be smuggled into rate-limit keys or audit rows.
func ClientIPHeaders ¶
ClientIPHeaders replaces the headers a trusted proxy's client address is read from (default X-Forwarded-For, then X-Real-IP), for a proxy that writes another, such as CF-Connecting-IP or Fly-Client-IP. HeaderForwarded (RFC 7239) is read by its for= parameters, with the same rightmost rule as X-Forwarded-For. It has no effect without TrustedProxies: a Forwarded header from any other address is ignored like every other forwarded header, because a client can write it.
func CookieDomain ¶
CookieDomain shares the session with every subdomain of domain, such as "example.com" for app.example.com and admin.example.com. The cookies are then named DomainCookieName and DomainFlowCookieName.
Every subdomain can then read and set the session cookie, including one that serves user content or runs someone else's code. Use it only when every subdomain is yours and trusted; the default host-only cookie cannot be touched by any other host. Pass the same option to Handler, Middleware and every adapter, or they disagree about which cookie is the session.
func CookieDomainFromBaseURL ¶
func CookieDomainFromBaseURL() Option
CookieDomainFromBaseURL is CookieDomain with the domain taken per request from the host of auth's BaseURL for that request, as better-auth's crossSubDomainCookies derives it from a dynamic base URL: a request to preview-42.example.com under auth.WithAllowedHosts sets cookies for preview-42.example.com and its subdomains. The host is one WithAllowedHosts accepted, or the static base URL's when it accepted none, so a forged Host header cannot choose the cookie's scope. The cookies are named like CookieDomain's (__Secure-), and the same warning applies: every subdomain of that host can read them. An explicit CookieDomain wins over it.
func CookiePrefix ¶
CookiePrefix replaces the "auth" in every cookie name: CookiePrefix("myapp") names the session cookie __Host-myapp_session (or __Secure-myapp_session under CookieDomain), and the flow, cache, device and account cookies the same way. Two apps on one host, or an app whose cookie policy names its cookies, use it. The browser prefix (__Host- or __Secure-) cannot be removed: it is what makes the browser enforce Secure, Path=/ and the domain rule. Pass the same option to Handler, Middleware and every adapter, as with CookieDomain, or they disagree about which cookie is the session.
It panics on an empty prefix or one that is not a cookie-name token (RFC 6265: no spaces, separators or control characters), because net/http would otherwise drop every Set-Cookie without a word and nobody could sign in.
func EncodeErrors ¶
func EncodeErrors(enc ErrorEncoder) Option
EncodeErrors makes enc write every error body that Handler, Middleware, OperationHandler, RequireUser, RequireFunc and RequireScope answer, and the gin, echo and fiber adapters through them. The status, Retry-After and the decision of what a client may learn stay the library's (see ErrorEncoder). nil keeps the library's JSON.
func ErrorPage ¶
func ErrorPage(opts ErrorPageOptions) Option
ErrorPage makes Handler serve GET prefix+ErrorPagePath: a page that shows a failed flow's error code, its message in the request's language and the provider's description, for auth.WithErrorURL to point at. Without it the route does not exist. The code is shown only when it is shaped like one, "unknown" otherwise, and the message comes from the library's own declarations, never from the link, so a crafted URL can put no text on the page but the escaped description.
func Health ¶
func Health() Option
Health makes Handler answer GET prefix+HealthPath with {"ok":true}, for a liveness probe or a smoke test after a deploy. It is opt-in so an existing route table gains nothing it did not ask for, and it reads no database, so a green answer means "mounted and running", not "ready to sign people in".
func LocaleCookie ¶
LocaleCookie reads the request's locale from the named cookie (DefaultLocaleCookieName when name is "") before Accept-Language, so a language the user chose in the app wins over the browser's list. The value is reduced the way LocaleOf reduces a header; the app sets the cookie.
func LocaleFunc ¶
LocaleFunc lets the app decide the request's locale: fn runs first, and a non-empty answer (reduced like an Accept-Language value, so "fr-CA;q=0.9" is "fr-CA") wins over the cookie and the header. It is for a source the transport cannot know: the app's own cookie or header, a tenant's default, a preference it looks up by its own means. fn runs before the session is looked up, because the lookup's own error is rendered in the locale, so a preference stored on the user is reachable only through what the request itself carries.
func Logger ¶
Logger sets where internal errors are logged. Their text never reaches the client; without a logger they go to slog.Default().
func OpenAPI ¶
func OpenAPI(info OpenAPIInfo) Option
OpenAPI makes Handler serve GET prefix+OpenAPIPath: the OpenAPI 3.1 document of every operation it serves, with input and output schemas derived from the operations' Go types. Opt-in, like Health, so an existing route table gains nothing it did not ask for; the document is public, because it describes routes a client can discover by calling them anyway and holds no deployment value.
func PartitionedCookies ¶
func PartitionedCookies() Option
PartitionedCookies adds the Partitioned attribute (CHIPS) to every cookie, for an app embedded in another site's page: the browser then keeps one jar per top-level site, so a session established inside site A's frame is not sent from site B's. It only strengthens: the cookies stay Secure, HttpOnly and __Host- or __Secure-, which is what Partitioned requires.
func Prefix ¶
Prefix sets the path the handler is mounted at, such as "/auth". It must match the pattern the handler is registered under.
func Reference ¶
func Reference(opts ReferenceOptions) Option
Reference makes Handler serve GET prefix+ReferencePath: an HTML page that loads an OpenAPI viewer and points it at the document OpenAPI serves, so the routes an app wired can be browsed and tried from a browser. It needs OpenAPI, and Handler panics at start-up without it, because a page that renders a document nobody serves is a misconfiguration, not a runtime case. Opt-in, like OpenAPI: it is a development convenience, and it loads a script from a third party by default (DefaultReferenceScript), which an app decides for itself; the page itself holds nothing but two script tags.
func RenameCookie ¶
func RenameCookie(role CookieRole, name string) Option
RenameCookie names one cookie, better-auth's advanced.cookies.<cookie>.name: RenameCookie(CookieSession, "sid") sets the session cookie as __Host-sid (__Secure-sid under CookieDomain), and the other cookies keep their prefix-built names. Only the name changes: the browser prefix stays, because it is what makes the browser enforce Secure, Path=/ and the domain rule, and the attributes (HttpOnly, Secure, SameSite) are the ones SameSiteStrict and PartitionedCookies set. Pass the same options to Handler, Middleware and every adapter, as with CookiePrefix.
It panics on an unknown role, or a name that is empty or not a cookie-name token, and Handler, Middleware and Begin panic when two cookies end up with one name, because each mistake would otherwise make the browser drop or overwrite a cookie and nobody could sign in.
func SameSiteStrict ¶
func SameSiteStrict() Option
SameSiteStrict sends the session, cache, device and account cookies with SameSite=Strict instead of Lax: a top-level navigation from another site (a link in a mail, a redirect from a provider) then arrives with no session, so the landing page cannot act on it. The trade-off is after the OAuth return: browsers treat a redirect chain that started at the provider as cross-site, so the first page after the callback may render signed out; the app's landing page then navigates once more, or loads the session from script. The flow cookie stays Lax whatever this option says, because it exists for that very return and a Strict one would never arrive.
func SkipTrailingSlash ¶
func SkipTrailingSlash() Option
SkipTrailingSlash makes Handler route "/auth/sign-out/" as "/auth/sign-out". Off by default: routes are exact, and a trailing slash is a 404.
func TrustedProxies ¶
TrustedProxies names the networks whose X-Forwarded-For (then X-Real-IP, or the headers ClientIPHeaders sets) is believed, as CIDRs or single addresses ("10.0.0.0/8", "fd00::/8", "192.0.2.10"). A request from any other address keeps its connection address, so a client that invents the header gains nothing; a request from a trusted proxy takes the rightmost forwarded address that is not itself a trusted proxy, which is the one the nearest trusted hop appended. Without this option the client IP is always the connection's address, as before. A value that does not parse panics at start-up, because a deployment that believes the wrong network must not start.
func WeakenCookie ¶
func WeakenCookie(role CookieRole, a CookieAttributes) Option
WeakenCookie is WeakenCookies for one cookie (session, flow, cache or device): better-auth's advanced.cookies.<cookie>.attributes. It replaces WeakenCookies' attributes for that cookie.
func WeakenCookies ¶
func WeakenCookies(a CookieAttributes) Option
WeakenCookies turns protections off on every cookie this package sets: better-auth's defaultCookieAttributes. An escape hatch: off by default, warned about at every start (WarnCookiesWeakened), and each field's risk is in its doc. WeakenCookie overrides it for one cookie.
type PluginMiddleware ¶
type PluginMiddleware struct {
// Paths are the routes it runs on, after the Handler prefix: an exact
// path ("/email/sign-in") or a prefix ending in "*" ("/email/*").
// Empty means every request Handler receives, including one for an
// unknown route.
Paths []string
Wrap func(next http.Handler) http.Handler
}
PluginMiddleware is net/http middleware a plugin adds around Handler's routes, the Go form of a better-auth plugin's onRequest and onResponse (Paths empty: every request) and of its middlewares with a path matcher (Paths set). Wrap receives the next handler and may replace the request, answer early without calling next, or wrap the ResponseWriter to see the response, which is everything onRequest and onResponse can do.
type ReferenceOptions ¶
type ReferenceOptions struct {
// ScriptURL is the viewer script; empty means DefaultReferenceScript.
ScriptURL string
// Nonce, when set, is called per request and its answer becomes the
// nonce attribute of both script tags, for an app whose
// Content-Security-Policy allows scripts by nonce. The app's own
// middleware sets the policy header; the page only carries the nonce.
Nonce func(r *http.Request) string
// Theme is the viewer's colour theme, better-auth's openAPI theme
// option; empty keeps the viewer's own default. Reference panics at
// start-up on a value outside ReferenceThemeValues, as it does for every
// misconfiguration it can see.
Theme ReferenceTheme
}
ReferenceOptions configures the page Reference serves.
type ReferenceTheme ¶
type ReferenceTheme string
ReferenceTheme is one of the viewer's (Scalar's) named themes.
const ( ThemeDefault ReferenceTheme = "default" ThemeAlternate ReferenceTheme = "alternate" ThemeMoon ReferenceTheme = "moon" ThemePurple ReferenceTheme = "purple" ThemeSolarized ReferenceTheme = "solarized" ThemeBluePlanet ReferenceTheme = "bluePlanet" ThemeSaturn ReferenceTheme = "saturn" ThemeKepler ReferenceTheme = "kepler" ThemeMars ReferenceTheme = "mars" ThemeDeepSpace ReferenceTheme = "deepSpace" ThemeLaserwave ReferenceTheme = "laserwave" // ThemeNone loads no theme stylesheet, for a page the app styles. ThemeNone ReferenceTheme = "none" )
The themes the default viewer ships, the same set better-auth's openAPI plugin accepts.
type SignInMethod ¶
type SignInMethod struct {
// ID is unique among the methods, such as "oauth.github". A page keys
// its translation on it ("authui.method." + ID) and a template may key
// an icon on it.
ID string
// Kind groups methods for styling: "oauth", "sso", "passkey". A page
// adds it as a class.
Kind string
// Label is the English button text, such as "Continue with GitHub".
Label string
// Route is the operation's route under the Handler prefix, such as
// "/oauth/github/start" (auth.Operation.Route). It must start with one
// "/": a page refuses anything else at start-up, so no method can
// become a link to another site.
Route string
// Field, when set, makes the method a one-input GET form instead of a
// link: the input's name, sent as a query parameter to Route, such as
// "email" for an SSO start that finds the company by the address.
Field string
// FieldLabel is the English label of that input.
FieldLabel string
}
SignInMethod is one way to sign in that a page can offer as a button or a link: "Continue with GitHub", "Sign in with your company". It names an operation route, not a URL, because only the app knows where it mounted Handler; a page joins the route to that prefix (authui's APIPrefix).
It is a browser navigation, a GET, on purpose: the route it names is a Query that answers with a redirect (an OAuth start, an SSO start), so a page offering it posts nothing and needs no origin check of its own. A method that needs JavaScript (a passkey ceremony) cannot be expressed as one and belongs in an app's own template.
func SignInMethods ¶
func SignInMethods(a *auth.Auth) []SignInMethod
SignInMethods collects every UIMethod plugin's methods, in plugin order, for a page that renders its own sign-in screen.
type TSMethod ¶
type TSMethod struct {
// Namespace is where the method sits: "" for the core's methods on the
// client's root, otherwise the plugin's factory and namespace name
// (password() and client.password).
Namespace string
// Plugin is the plugin that registered the operation, as auth names it
// (auth.CorePluginName for the core's).
Plugin string
// Name is the method's name: the operation id after the plugin's
// prefix, in camelCase ("organization.invite-member" is inviteMember).
Name string
// ID is the operation it calls.
ID auth.EndpointID
// HTTPMethod and Path are the route, Path without a mount prefix.
HTTPMethod, Path string
// Require is what the call must carry before the operation runs.
Require auth.Requirement
// Input and Output are the TypeScript types of the method's argument and
// of its result's data (EmptyInput when the operation takes no fields).
Input, Output string
}
TSMethod is one method of the client TypeScriptClient writes, for a reference page or a tool that lists the client's surface without parsing TypeScript (apiref writes docs/reference/ts-client.md from it).
type UIMethod ¶
type UIMethod interface {
auth.Plugin
SignInMethods() []SignInMethod
}
UIMethod is a plugin that offers sign-in methods to the pages an app renders, the ready-made authui pages included: an OAuth plugin lists one button per provider, an SSO plugin a "company sign-in" form. It is optional and read once, when the page handler is built.
It lives here rather than in authui so a plugin can implement it without importing authui, which is a module of its own.