oauth2

package
v0.31.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package oauth2 wires a go-oauth2/oauth2/v4 server into a nexus app and bridges its access-token store to nexus.auth so handlers can gate themselves with auth.Required() / auth.Requires().

Minimum config — password grant against a user store:

nexus.Run(
    nexus.Config{Server: nexus.ServerConfig{Addr: ":8080"}},
    oauth2.Module(oauth2.Config{
        Authenticator: func(ctx context.Context, clientID, username, password string) (string, error) {
            user, err := users.Authenticate(ctx, username, password)
            if err != nil { return "", err }
            return strconv.Itoa(int(user.ID)), nil
        },
    }),
)

Defaults are conservative: in-memory token store, anonymous client only, no JTI, "Bearer" token type, 5-minute identity cache. Production apps replace TokenStore (Redis-backed via NewCacheTokenStore), ClientStore (DB-backed via NewLoaderClientStore), and frequently set IdentityResolver to populate Identity.Roles / .Extra.

The package owns:

  • POST {Config.TokenPath} OAuth2 token endpoint
  • POST {Config.RevokePath} token revocation (when set)
  • bridge from token-store ↔ auth.Module

Three-legged authorization-code flow is reachable via Config.ServerCustomizer (set SetUserAuthorizationHandler and add the route yourself); the package doesn't mount it by default because most apps using this run password / client_credentials.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidCredentials = stderrors.New("oauth2: invalid credentials")
	ErrAccountDisabled    = stderrors.New("oauth2: account disabled")
	ErrAccountLocked      = stderrors.New("oauth2: account locked")
	ErrServiceUnavailable = stderrors.New("oauth2: service unavailable")
)

Sentinel errors callers can return from PasswordAuthenticator to get the standard, user-friendly OAuth2 response without writing their own ErrorMapper. Domain code stays free of OAuth2 types — just return one of these and the mapper does the translation.

View Source
var DefaultErrorMessages = map[error]string{
	ErrInvalidCredentials: "The username or password you entered is incorrect. Please try again.",
	ErrAccountDisabled:    "Your account has been disabled. Please contact your administrator.",
	ErrAccountLocked:      "Your account is locked due to too many failed sign-in attempts. Please contact your administrator.",
	ErrServiceUnavailable: "The sign-in service is temporarily unavailable. Please try again in a moment.",
}

DefaultErrorMessages maps each well-known sentinel to the description string emitted in the OAuth2 error response. Exported so apps can clone, edit (i18n, brand voice), and pass the result to NewErrorMapper.

View Source
var ErrCacheMiss = errors.New("oauth2: cache miss")

ErrCacheMiss is the sentinel implementations may return from Get when the key is absent. Treated identically to a non-nil error other than this one (i.e. "no token here") — tokens.go converts any miss/error into a nil TokenInfo so a misconfigured cache fails closed instead of crashing.

Functions

func DefaultErrorMapper

func DefaultErrorMapper(err error) *errors.Response

DefaultErrorMapper translates the bundled sentinel errors into OAuth2 responses with DefaultErrorMessages. Returns nil for any other error so go-oauth2's stock translation runs.

func Module

func Module(cfg Config) nexus.Option

Module wires the OAuth2 server into a nexus app. Returns a nexus.Option to compose into your top-level Module(...) chain.

func NewCacheTokenStore

func NewCacheTokenStore(cache Cache, prefix string) oauth2lib.TokenStore

NewCacheTokenStore returns an oauth2lib.TokenStore that persists tokens in the supplied Cache under keys prefixed with `prefix`.

Storage layout (4 keyspaces under prefix):

{prefix}basic:{uuid}      → JSON-encoded TokenInfo (the source of truth)
{prefix}access:{access}   → uuid pointing at the basic record
{prefix}refresh:{refresh} → uuid pointing at the basic record
{prefix}code:{code}       → JSON-encoded TokenInfo (auth-code grant)

Refreshing rewrites access/refresh keys but reuses the basic record so a refresh-then-revoke-old-access doesn't strand the refresh token.

func NewLoadedClient

func NewLoadedClient(c StaticClient) oauth2lib.ClientInfo

NewLoadedClient adapts a plain StaticClient into a ClientInfo — useful inside a LoaderFunc when you're reading rows from a DB and want the same secret-verification matrix as the static store.

func NewLoaderClientStore

func NewLoaderClientStore(load LoaderFunc) oauth2lib.ClientStore

NewLoaderClientStore wraps a LoaderFunc as a ClientStore. The loader is called on every GetByID — add caching inside the loader if your backend can't take the load.

func NewStaticClientStore

func NewStaticClientStore(clients ...StaticClient) oauth2lib.ClientStore

NewStaticClientStore returns a ClientStore over an in-memory list. Useful for tests and tiny apps; for production, prefer NewLoaderClientStore against your DB.

func SoftenStockMessages

func SoftenStockMessages(re *errors.Response)

SoftenStockMessages returns a ResponseErrorRewriter that replaces the worst of go-oauth2's stock descriptions with friendlier strings. Mirrors the rewriter in portal_admin/services/server.go. Pass directly as Config.ResponseErrorRewriter.

func VerifyBcrypt

func VerifyBcrypt(stored, input string) bool

VerifyBcrypt compares input against a bcrypt hash. Accepts both raw bcrypt strings and {bcrypt}-prefixed values; returns false for any other format. Constant-time semantics from x/crypto.

func VerifySpringPassword

func VerifySpringPassword(stored, input string) (ok bool, scheme string)

VerifySpringPassword tries every password format Spring Security's DelegatingPasswordEncoder emits in the wild, in priority order:

  • {bcrypt}$2a$… — modern Spring default
  • {noop}plain — disabled-encoding marker (test fixtures)
  • $2a$ / $2b$ / $2y$ — raw bcrypt without the {scheme} prefix
  • 40-char hex with a 10-char salt prefix — legacy salted-sha1 (Spring's StandardPasswordEncoder pre-5.0)

Returns (ok, scheme) — scheme is the matched bucket or "" on miss, useful for logging which path resolved the credential. Never logs the input.

Types

type Cache

type Cache interface {
	Get(ctx context.Context, key string) (string, error)
	Set(ctx context.Context, key, value string, ttl time.Duration) error
	Delete(ctx context.Context, key string) error
}

Cache is the small surface NewCacheTokenStore needs from a caller's cache layer. Any Redis / Memcached / in-memory wrapper that exposes string Get/Set/Delete with TTL fits — the package doesn't import a specific cache library.

type Config

type Config struct {
	// Authenticator validates credentials for the password grant.
	// Required for password-grant flows. Optional for pure
	// client_credentials apps — leave nil and only client_credentials
	// will issue tokens.
	Authenticator PasswordAuthenticator

	// ClientStore loads OAuth2 clients by ID. Defaults to a single
	// public anonymous client (id="anonymous", no secret) — fine for
	// trusted-network deploys and tests, wrong for the public
	// internet. Provide NewLoaderClientStore or NewStaticClientStore
	// for real workloads.
	ClientStore oauth2lib.ClientStore

	// TokenStore persists access/refresh/code tokens. Defaults to
	// go-oauth2's in-memory store (single-process only). Use
	// NewCacheTokenStore for horizontally-scaled apps.
	TokenStore oauth2lib.TokenStore

	// IdentityResolver enriches the auth.Identity built from each
	// resolved access token. Defaults to {ID: ti.GetUserID()} with
	// the raw token + TokenInfo on .Extra (a *Session) so logout
	// handlers can revoke without re-extracting.
	IdentityResolver IdentityResolver

	// ErrorMapper translates Authenticator errors → OAuth2 responses.
	// Defaults to DefaultErrorMapper (handles ErrInvalidCredentials,
	// ErrAccountDisabled, ErrAccountLocked, ErrServiceUnavailable).
	// Override or wrap to translate domain errors.
	ErrorMapper ErrorMapper

	// ResponseErrorRewriter runs after go-oauth2 builds an OAuth2
	// error response — use it to soften descriptions, translate
	// strings, or log. Optional.
	ResponseErrorRewriter func(*errors.Response)

	// TokenType is the value emitted as token_type in the response.
	// Defaults to "Bearer". Set to "bearer" (lowercase) when
	// migrating clients written against Spring's DefaultTokenServices.
	TokenType string

	// IncludeJTI adds a unique "jti" extension field to every
	// issued token, useful for revocation lists / replay tracking.
	// Off by default.
	IncludeJTI bool

	// AllowGetAccessRequest mirrors server.SetAllowGetAccessRequest.
	// Off by default — the OAuth2 spec recommends POST.
	AllowGetAccessRequest bool

	// TokenPath is the mount path for the token endpoint. Defaults
	// to "/oauth/token".
	TokenPath string

	// RevokePath, when non-empty, mounts a POST handler that
	// removes the access token from the TokenStore. Empty by default.
	RevokePath string

	// IdentityCache bounds how long a resolved auth.Identity stays
	// in the auth.Manager cache. Defaults to 5 minutes; set to
	// negative to disable caching.
	IdentityCache time.Duration

	// Manager is an escape hatch — when non-nil, the package uses
	// it verbatim and ignores ClientStore / TokenStore. Use for
	// exotic config (custom token generator, code-expiry policy)
	// the rest of Config doesn't expose.
	Manager *manage.Manager

	// ServerCustomizer runs after the *server.Server is built and
	// before Mount, giving callers access to any go-oauth2 setter
	// Config doesn't surface (UserAuthorizationHandler, ScopeHandler,
	// custom AccessTokenExpHandler, etc).
	ServerCustomizer func(*server.Server)

	// AuthExtra are auth.Config fields the caller wants to thread
	// through (OnResolve, OnFail, Permissions, error rewriters).
	// Resolve and Cache are owned by this package and ignored if set.
	AuthExtra auth.Config
}

Config drives oauth2.Module. The only required field is Authenticator (for password grant); everything else has a default.

type ErrorMapper

type ErrorMapper func(err error) *errors.Response

ErrorMapper translates an internal error (returned by Authenticator or any custom handler) into an OAuth2 *errors.Response. Return nil to fall through to go-oauth2's stock translation.

func NewErrorMapper

func NewErrorMapper(messages map[error]string) ErrorMapper

NewErrorMapper builds an ErrorMapper from a custom message table — use to override the bundled descriptions (i18n) without rewriting the switch logic.

type IdentityResolver

type IdentityResolver func(ctx context.Context, ti oauth2lib.TokenInfo) (*auth.Identity, error)

IdentityResolver turns a verified OAuth2 TokenInfo into an auth.Identity. Defaults to {ID: ti.GetUserID()} — override to populate Roles / Scopes / Extra from a user-profile lookup.

type LoaderFunc

type LoaderFunc func(ctx context.Context, id string) (oauth2lib.ClientInfo, error)

LoaderFunc loads a client record by ID. Implementations typically hit a DB or cache; the result must satisfy oauth2lib.ClientInfo. Wrap a pure-data record with NewLoadedClient when you only have the fields, not the interface.

type PasswordAuthenticator

type PasswordAuthenticator func(ctx context.Context, clientID, username, password string) (userID string, err error)

PasswordAuthenticator validates a (username, password) pair against the app's user store and returns the user ID that becomes TokenInfo.GetUserID() (and downstream auth.Identity.ID). Return a typed error and ErrorMapper translates it into the OAuth2 response; return ErrServiceUnavailable / ErrAccountLocked / etc. from the package-level vars to get the standard messages for free.

type Server

type Server struct {
	*nexus.Service
	Srv     *server.Server
	Manager *manage.Manager
	// contains filtered or unexported fields
}

Server is the wired OAuth2 server. Exported so handlers (revoke, custom userinfo, etc.) can reach the underlying *server.Server and *manage.Manager when ServerCustomizer isn't enough.

func (*Server) HandleRevoke

func (s *Server) HandleRevoke(c *gin.Context)

HandleRevoke removes the bearer token from the TokenStore. Mounted under RevokePath when set. Returns 204 on success / unknown token (idempotent) so clients can safely retry.

func (*Server) HandleToken

func (s *Server) HandleToken(c *gin.Context)

HandleToken is the OAuth2 token endpoint. Mounted under TokenPath.

func (*Server) Resolve

func (s *Server) Resolve(ctx context.Context, token string) (*auth.Identity, error)

Resolve loads an access token and returns its Identity. Wired into auth.Module via the holder pattern below so Resolve has a stable reference even though the *Server is built later in fx startup.

type Session

type Session struct {
	Token string
	Info  oauth2lib.TokenInfo
}

Session is the default Identity.Extra payload — carries the raw token + TokenInfo so a logout handler can revoke both the token store entry and the auth cache without re-extracting headers.

type StaticClient

type StaticClient struct {
	ID, Secret, Domain, UserID string
	Public                     bool
}

StaticClient is the data shape used by NewStaticClientStore.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL