Documentation
¶
Overview ¶
Package auth is nexus's built-in authentication surface. It owns the plumbing — token extraction, identity caching, per-op enforcement, context propagation — while leaving the *resolution* step (token → Identity) user-supplied. That keeps auth.Module unopinionated: works with JWTs, opaque bearer tokens, API keys, session cookies, or any custom scheme, as long as the caller can turn a raw token into an *auth.Identity.
Minimal wiring:
nexus.Run(nexus.Config{...},
auth.Module(auth.Config{
Resolve: func(ctx context.Context, tok string) (*auth.Identity, error) {
u, err := myAPI.ValidateToken(ctx, tok)
if err != nil { return nil, err }
return &auth.Identity{
ID: u.ID,
Roles: u.Roles,
Extra: u,
}, nil
},
Cache: auth.CacheFor(15 * time.Minute),
}),
advertsModule,
)
Per-op enforcement (cross-transport — same bundle works on REST + GraphQL via the existing nexus.Use attachment):
nexus.AsMutation(NewCreateAdvert,
auth.Required(), // 401 if no valid identity
auth.Requires("ROLE_CREATE_ADVERT"), // 403 if missing permission
)
Resolver access from a handler:
func NewListAdverts(db *DB) func(ctx context.Context) ([]Advert, error) {
return func(ctx context.Context) ([]Advert, error) {
user, ok := auth.User[MyUser](ctx)
if !ok { /* Required() would have caught this earlier */ }
return db.ListFor(user.ID)
}
}
Coexistence with the existing (*Service).Auth API: auth.Module operates at the app layer via a global middleware, so services that still call (*Service).Auth(UserDetailsFn) keep working as before. Over time, migrate resolvers from graph.GetRootInfo to auth.IdentityFrom/User.
Index ¶
- Variables
- func DefaultPermissions(id *Identity, required []string) bool
- func Module(cfg Config) nexus.Option
- func Optional() nexus.MiddlewareOption
- func Required() nexus.MiddlewareOption
- func Requires(perms ...string) nexus.MiddlewareOption
- func User[T any](ctx context.Context) (*T, bool)
- func WithIdentity(ctx context.Context, id *Identity) context.Context
- type CacheOption
- type Config
- type Extractor
- type ExtractorFunc
- type Identity
- type PermissionFn
- type Resolver
Constants ¶
This section is empty.
Variables ¶
var ErrForbidden = errors.New("auth: forbidden")
ErrForbidden is returned when an identity is present but lacks the required permissions. Middleware converts this to 403.
var ErrUnauthenticated = errors.New("auth: unauthenticated")
ErrUnauthenticated is returned by helpers when no identity is on ctx. Middleware converts this to 401 / GraphQL error uniformly.
Functions ¶
func DefaultPermissions ¶
DefaultPermissions is the built-in permission check: every required permission must appear in the identity's Roles or Scopes.
func Module ¶
Module wires auth into the nexus app:
- Installs a global middleware that extracts + (optionally caches) resolves the identity per request, then stashes it on the request context.
- Stashes the shared moduleState on the context so per-op Required / Requires bundles can read custom PermissionFn / cache config.
- Registers a few "auth" middleware names in the registry so the dashboard's middleware chip list labels them consistently.
Module does NOT touch (*Service).Auth. Services using the older UserDetailsFn hook continue to work alongside; migration is a per-resolver switch from graph.GetRootInfo to auth.User[T].
func Optional ¶
func Optional() nexus.MiddlewareOption
Optional is a no-op bundle that exists purely as dashboard signal — it labels the endpoint as auth-aware without enforcing presence. Useful for public endpoints that still personalize when a user is logged in, so the UI surfaces "this endpoint reads identity".
func Required ¶
func Required() nexus.MiddlewareOption
Required returns a cross-transport middleware bundle that rejects any request lacking a resolved Identity on ctx. 401 on REST, a graphql- native error on GraphQL — same bundle attaches cleanly to both.
nexus.AsMutation(NewCreateAdvert, auth.Required())
func Requires ¶
func Requires(perms ...string) nexus.MiddlewareOption
Requires returns a cross-transport bundle that rejects requests whose Identity doesn't satisfy every listed permission (roles / scopes). Implies authentication — attaching Requires without Required still 401s on anonymous requests, because you can't evaluate permissions on a nil identity.
nexus.AsMutation(NewCreateAdvert, auth.Requires("ROLE_CREATE_ADVERT"))
Types ¶
type CacheOption ¶
type CacheOption struct {
// TTL is how long a resolved identity stays in cache. 0 disables.
TTL time.Duration
// MaxEntries bounds the cache so a misbehaving client can't OOM
// the app by sending many unique tokens. 0 means unbounded.
MaxEntries int
}
CacheOption configures how resolved identities are memoized in-memory. The cache is process-local on purpose — auth state should be short- lived (minutes), and a cross-process cache adds invalidation pain that's rarely worth it. Callers that need cross-process cache can handle it inside their Resolve function.
func CacheFor ¶
func CacheFor(ttl time.Duration) CacheOption
CacheFor is a one-liner for the common case — time-only TTL. Entries are bounded to 4096 by default so an attacker firing endless distinct tokens can't trigger unbounded growth.
type Config ¶
type Config struct {
// Extract pulls the raw token from the request. Defaults to Bearer()
// (Authorization: Bearer <token>). Combine strategies with Chain.
Extract Extractor
// Resolve is REQUIRED — the function that turns a raw token into an
// Identity. The package owns extraction, caching, and enforcement;
// Resolve is the single plug the caller supplies.
Resolve Resolver
// Cache memoizes resolved identities so the backend call fires at
// most once per TTL per token. Zero TTL disables caching entirely.
Cache CacheOption
// Permissions overrides the default roles+scopes check. Useful when
// an app has a hierarchical role model or non-trivial scope matching.
Permissions PermissionFn
// OnResolve fires after every successful resolution — good for
// audit logging or per-user metrics.
OnResolve func(ctx context.Context, id *Identity)
// OnFail fires on extraction / resolution failure. The token is
// passed so handlers can log prefixes for diagnostics; do NOT log
// the full token in production.
OnFail func(ctx context.Context, token string, err error)
}
Config drives auth.Module. Only Resolve is required; everything else has a sensible default.
type Extractor ¶
Extractor pulls a raw token from an HTTP request. Returns ("", false) when no token is present — callers treat that as "anonymous request" rather than an error, so public endpoints still resolve a nil Identity and proceed.
func APIKey ¶
APIKey reads a named header (e.g. "X-API-Key"). Trailing whitespace stripped for ergonomics; empty values are treated as absent.
func Bearer ¶
func Bearer() Extractor
Bearer reads "Authorization: Bearer <token>". Case-insensitive on the scheme name per RFC 7235. Empty tokens ("Bearer ") are treated as absent so downstream Required() correctly fires 401.
type ExtractorFunc ¶
ExtractorFunc adapts a plain function to the Extractor interface.
type Identity ¶
Identity is the resolved authenticated user. Roles and Scopes are the two first-class permission buckets; Extra carries any backend-specific payload the caller wants to thread through to resolvers.
func IdentityFrom ¶
IdentityFrom returns the Identity on ctx, if any. Returns (nil, false) for anonymous requests — Required() is what turns that into a 401.
type PermissionFn ¶
PermissionFn decides whether an identity satisfies a set of required permissions. The built-in default (DefaultPermissions) requires the identity to have every listed permission in Roles or Scopes.