go-auth
Guides

Integration contract

Named Go inputs, HTTP errors, authentication policy, and resource ownership.

Integration contract

The public Auth.Services() interfaces are the contract for custom transports and application code. Import github.com/nazimdjebloun/go-auth/api for operation inputs. Mounted HTTP routes call those same operations and add cookie handling, authentication middleware, CSRF checks, and rate limiting.

Application HTTP routes

Use Auth.Protect("POST /widgets/{id}", handler) for the complete browser protection chain on your own endpoints. Add role, app-permission or organization wrappers inside it so they run after authentication. Patterns use ServeMux syntax for routing, rate selection and exemptions. Put Auth.CORS around a method-selecting router so preflights run before dispatch. See Protect application routes for a full example, failure behavior and third-party router integration.

Migrating Go callers

Service operations with multiple values after context.Context now take one named input. This is a breaking Go API change before v1; return values and HTTP request bodies are unchanged. Single-value operations, such as Session.Validate(ctx, token) and OAuth.Initiate(ctx, provider), keep their direct argument.

// Previously: ChangeName(ctx, userID, name)
err := auth.Services().Auth.ChangeName(ctx, api.ChangeNameInput{
    UserID: userID,
    Name:   name,
})

verified, err := auth.Services().TwoFactor.Verify(ctx, api.TwoFactorVerifyInput{
    ChallengeID:  challengeID,
    BindingToken: bindingCookie,
    Code:         code,
    IP:           clientIP,
    UserAgent:    userAgent,
})

Move the old positional arguments into the following fields, in the order shown. All types are in api.

Service operationInput typeFields replacing positional arguments
Admin.GetStatsGetAdminStatsInputActorID, ActorSessionID
Auth.ChangeNameChangeNameInputUserID, Name
Auth.DeleteAccountDeleteAccountInputUserID, Password
Session.CreateCreateSessionInputUserID, IP, UserAgent
Session.TouchTouchSessionInputToken, LastActiveAt
Session.RevokeByIDForUserRevokeSessionForUserInputSessionID, UserID
Session.RevokeManyForUserRevokeSessionsForUserInputSessionIDs, UserID
Session.RevokeAllExceptRevokeAllSessionsExceptInputUserID, ExceptSessionID
Session.ListListSessionsInputUserID, Offset, Limit
OAuth.InitiateLinkOAuthLinkInputProvider, UserID, SessionTokenHash
OAuth.CallbackOAuthCallbackInputProvider, Code, State, BrowserState, SessionToken, IP, UserAgent
OAuth.UnlinkOAuthUnlinkInputUserID, Provider
TwoFactor.VerifyTwoFactorVerifyInputChallengeID, BindingToken, Code, IP, UserAgent
TwoFactor.ResendTwoFactorResendInputChallengeID, BindingToken
TwoFactor.EnableTwoFactorEnableInputUserID, Password, KeepOtherSessions, CallerSessionID
TwoFactor.DisableTwoFactorDisableInputUserID, Password
Invite.HardDeleteInviteHardDeleteInviteInputInviteID, ActorID
Invite.RevokeInviteRevokeInviteInputInviteID, ActorID
Invite.ResendInviteEmailResendInviteEmailInputInviteID, ActorID
OrgInvite.DeleteOrgInviteDeleteOrgInviteInputOrgID, InviteID, ActorID
OrgInvite.ResendOrgInviteEmailResendOrgInviteEmailInputOrgID, InviteID, ActorID

Update mocks and custom implementations of the public capability interfaces too. In particular, middleware.SessionAuthenticator.Touch now has this signature:

Touch(ctx context.Context, input api.TouchSessionInput) error

Named fields do not establish trust. Set actor/user IDs from the authenticated identity, client metadata from your transport, and binding/session credentials from their original cookies or equivalent trusted storage. Built-in handlers decode transport-specific bodies and supply actor identity separately; never accept it from request JSON. Public HTTP business fields use camelCase. Inputs can still contain passwords and one-time codes; do not log them wholesale.

For OAuth callbacks, keep State (provider callback query) separate from BrowserState (the initiating browser's state cookie). Linking also needs the current raw session token in SessionToken. The service compares these credentials; an input struct does not replace the browser-binding checks.

Direct service calls do not execute HTTP middleware. Your custom transport must supply authentication and route-level authorization, rate limiting, and any applicable browser CSRF protection. Low-level operations such as session creation assume the caller has already completed the required authentication flow. Optional service capabilities can be nil when disabled; check before using them.

Errors across transports

Go operations return ordinary errors. Use errors.Is for a sentinel and errors.As for *domain.AuthError; backend errors may instead wrap their original cause. Do not send raw backend errors to clients.

The built-in HTTP handlers and middleware use {"error": code, "message": message} with Content-Type: application/json. CSRF rejection bodies now use this envelope too, replacing their former plain-text bodies. Match the code rather than parsing the message. OAuth callback failures remain browser redirects with an error query parameter. Router-generated errors and your own handlers are outside this contract. See Error Handling for codes and statuses.

Frontend authentication outcomes

Successful HTTP authentication flows return one step instead of requires* booleans. OAuth callbacks use the same names in query parameters. See Authentication outcomes for the decision table, response fields and email-verification continuation behavior. Go service result fields and the HTTP error envelope are unchanged.

Authentication policy

Password and OAuth login apply the same global and per-account email-2FA requirements. Admin OAuth also applies the admin default. Authentication and signup methods are independently selected with WithAuthentication and WithRegistration. The policy matrix specifies verification and second-factor behavior for each path.

Optional application authorization

WithAppPermissions(AppPermissionsConfig{Enable:true}) exposes Services.AppPermissions. It is nil when disabled. Enabled mode authorizes through the account's single appRoleId. Enabled-mode returned users have an empty Go User.Role and omit the account role JSON field. Disabled mode supports the fixed admin and user roles and returns role in account JSON. Organization membership roles remain separate.

App permission operations take a trusted api.AppPermissionActor containing the authenticated user ID and current session ID. Existing administrative inputs use ActorSessionID alongside their actor/admin ID. Direct service calls enforce the action key and required session assurance too. For stats, use Admin.GetStats(ctx, api.GetAdminStatsInput{ActorID: id, ActorSessionID: sessionID}). It replaces both GetStats(ctx, id) and GetStatsForActor; the permission and required assurance checks are unchanged. Obtain these IDs from authenticated server context, never a client request body.

Only protected administrators require local second-factor verification by default, including OAuth login. Every other role follows the user MFA policy, even with delegated library grants. Custom operators use ordinary login; admin login remains reserved for protected administrators. See the authentication policy matrix. Use AppPermissions.RequirePermission for backend checks and Auth.RequireAppPermission after authentication on consumer HTTP routes. Self-access responses are informational and do not authorize writes.

See App roles and permissions for initialization, selective library provisioning, business definitions, revisions and delegation limits. Use the canonical schema; startup installs no permissions.

Auth migration ownership

Auth owns its fixed tables and the goauth_migrations journal. Run the matching goauth migrate independently of application/ORM migrations, before starting services and workers. Keep app fields in extension tables with foreign keys; do not include auth SQL in application desired schemas or copied host migrations. New checks database connectivity and recorded migration compatibility before starting workers, including for borrowed handles. It never executes DDL. See Migrations for adoption and recovery.

Startup and shutdown

NewConfig validates configuration and starts no background work. New clones and revalidates it, opens or adopts the database, constructs services, and starts the configured workers. Depending on configuration, these include maintenance, recovery-email delivery, audit delivery/retention, and cleanup for the library's in-memory rate-limit stores. Create an Auth for the application's lifetime, rather than per request.

Stop accepting work and wait for HTTP requests and direct service callers to finish before calling Auth.Close(). Close does not stop or drain your HTTP server. Call it once as part of orderly application shutdown, then release resources you supplied.

ResourceOwnership and shutdown
Database opened from DatabaseConfig.URLAuth.Close closes it.
Supplied DatabaseConfig.DB (*sql.DB)Remains open; the caller closes it after Auth.Close.
Supplied DatabaseConfig.Pool (*pgxpool.Pool)Auth.Close closes its SQL adapter, leaving the caller's pool open.
Default route rate-limit store and internal 2FA notification storeCreated and closed by go-auth.
Supplied rate-limit storeRemains caller-owned, including its cleanup lifecycle.
Supplied mailer, audit sinks, and OAuth providersTheir lifecycle remains caller-owned; Auth.Close does not close them.

Close stops the recovery worker and maintenance before stopping audit work, then releases the owned stores and database. The audit stop receives a five-second timeout. There is no deadline for the whole Close call and it returns no error; custom dependencies must honor cancellation so workers can exit. A stop timeout is not a guarantee that external audit delivery finished: undelivered durable outbox records remain for later processing. If you use an asynchronous mailer, stop/drain that caller-owned mailer after Auth.Close.

Browser and CSRF migration

This is a breaking pre-v1 browser API change. Replace WithCookie(...) with WithBrowser(BrowserConfig{APIURL: ..., AllowedOrigins: ..., Cookies: ...}); move the origins out of SecurityConfig. Keep password policy, email-link policy, and pepper timestamps in WithSecurity, and lifetimes in WithSession.

Remove DisableCSRFToken, AllowMissingCSRFHeaders, CSRFToken, and their token overrides; there are no replacement bypasses. Remove TwoFactorConfig.DisableChallengeBinding and move ChallengeCookieName to BrowserConfig.Cookies.ChallengeName. Keep cookie domains empty for normal subdomain deployments.

Update clients to read 200 {"token":"..."} from /auth/csrf-token, send credentials and the token header, and consume X-CSRF-Token replacements. Do not read the HttpOnly CSRF cookie. Custom cookie helpers now require the request and return errors: SetSessionCookies(w, r, sessionID, sessionToken, refreshToken), ClearSessionCookies(w, r), and RotateCSRFToken(w, r). Issuing authentication cookies includes CSRF rotation; do not issue it separately. api.OAuthCallbackResult.SessionID supplies the stable ID for custom OAuth transports.

The low-level middleware dependency struct is not an application configuration API. The facade wires session identity resolution and validated snapshot reuse; custom HTTP routes should use Auth.RequireCSRF and Auth.RequireAuth. Identity lookup for CSRF does not authorize expired, revoked, banned, or otherwise invalid sessions.

Custom admin HTTP transports must select admin consistently through the request header, including when calling request-aware cookie helpers with credentials returned by AdminLogin. A helper rejects credentials from a different context instead of writing them into the wrong cookie set. Pure Go service calls have no browser header; admin login returns admin credentials, 2FA verification restores the challenge's context, and refresh preserves the original credential context.

For direct browser access to custom API routes, register each path's OPTIONS handler with CORS outermost, before authentication and rate limiting. A method-specific POST/PATCH/DELETE route alone does not handle preflight. Auth.Mount already registers OPTIONS for go-auth's own paths; consumers own preflight for their application paths.

Update admin clients to send X-GoAuth-Session: admin on every request, including bootstrap and 2FA follow-ups. Admin login always issues the admin context's credential cookies. Old shared admin cookies are not reused as admin credentials; sign in again. App cookie names remain unchanged. Optional Cookies.AdminName, AdminRefreshName, and AdminChallengeName customize the independent admin names; transport policy remains common to both contexts. The bundled panel handles selection automatically, with VITE_API_URL pointing directly at the backend.

Old CSRF tokens are incompatible and safe bootstrap requests replace them. No database schema migration is needed. Review HTTPS/HTTP development settings and direct API deployment using Deployment.

Session access checks and verification-only identity lookup

Use Auth.GetSession or Auth.CheckSession when authorizing ordinary programmatic access. They apply the current email-verification requirement as well as session liveness and account status. GetSession returns email_not_verified and CheckSession returns false for a restricted account. Auth.RequireAuth applies the same verification requirement to custom protected HTTP routes.

The low-level Services().Session.ValidateWithUser checks session identity and liveness without imposing email verification. It remains available to build verification, minimal-status and logout flows. Its successful return is not permission to bypass verification for ordinary protected operations. The built-in /auth/me exposes only minimal verification status while restricted; /auth/refresh rotates cookies but similarly omits the session body.

On this page