auth

package module
v1.13.2 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 12 Imported by: 6

README

Forge Auth, authentication providers and route middleware for Go APIs built on Forge

Forge Auth: API authentication middleware for Go

Forge Auth is the authentication package for Forge, the Go backend framework. It gives you ready-made providers for API keys, Bearer tokens such as JWTs, HTTP Basic, OAuth 2.0, OpenID Connect and LDAP or Active Directory, a registry that chains them, and route middleware that checks scopes, roles and permissions before your handler runs. You write the part that knows your users (a validator function), and the package handles reading the credential, trying providers in order and answering 401 or 403.

Go CI Go Reference Apache 2.0

Documentation · Quick start · Features · Providers · Protecting routes · Authorization · The extension · FAQ

Quick start

You need Go 1.26 or later. Add Forge and the auth module:

go get github.com/xraph/forge@latest
go get github.com/xraph/forge/extensions/auth@latest

Save this as main.go. It protects one route with an API key and a second one with an API key plus the admin role:

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/xraph/forge"
	"github.com/xraph/forge/extensions/auth"
	"github.com/xraph/forge/extensions/auth/providers"
)

// Demo keys. A real service looks these up in its database.
var apiKeys = map[string]auth.AuthContext{
	"alice-key": {Subject: "alice", Roles: []string{"admin"}},
	"bob-key":   {Subject: "bob", Roles: []string{"viewer"}},
}

func validateKey(_ context.Context, key string) (*auth.AuthContext, error) {
	authCtx, ok := apiKeys[key]
	if !ok {
		return nil, auth.ErrInvalidCredentials
	}

	return &authCtx, nil
}

func main() {
	app := forge.NewApp(forge.AppConfig{
		Name:        "auth-demo",
		HTTPAddress: ":8080",
	})

	registry := auth.NewRegistry(app.Container(), app.Logger())

	err := registry.Register(providers.NewAPIKeyProvider("api-key",
		providers.WithAPIKeyValidator(validateKey),
	))
	if err != nil {
		log.Fatal(err)
	}

	signedIn := registry.MiddlewareWithRequirement(auth.Requirement{
		Providers: []string{"api-key"},
	})
	adminsOnly := registry.MiddlewareWithRequirement(auth.Requirement{
		Providers: []string{"api-key"},
		Roles:     []string{"admin"},
	})

	router := app.Router()

	err = router.GET("/me", func(ctx forge.Context) error {
		authCtx, ok := auth.GetAuthContext(ctx)
		if !ok {
			return forge.Unauthorized("not signed in")
		}

		return ctx.JSON(http.StatusOK, map[string]any{
			"subject":  authCtx.Subject,
			"roles":    authCtx.Roles,
			"provider": authCtx.ProviderName,
		})
	}, forge.WithMiddleware(signedIn), forge.WithAuth("api-key"))
	if err != nil {
		log.Fatal(err)
	}

	err = router.DELETE("/cache", func(ctx forge.Context) error {
		return ctx.JSON(http.StatusOK, map[string]string{"status": "cleared"})
	}, forge.WithMiddleware(adminsOnly), forge.WithAuth("api-key"), forge.WithAnyRole("admin"))
	if err != nil {
		log.Fatal(err)
	}

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

Run it and try a few requests:

go mod tidy
go run .

curl -i http://localhost:8080/me
# 401 Unauthorized
curl -H "X-API-Key: alice-key" http://localhost:8080/me
# {"provider":"api-key","roles":["admin"],"subject":"alice"}
curl -i -X DELETE -H "X-API-Key: bob-key" http://localhost:8080/cache
# 403 authorization failed: requires any of roles admin
curl -X DELETE -H "X-API-Key: alice-key" http://localhost:8080/cache
# {"status":"cleared"}

One thing in that program is there on purpose, and you'll want it in your own code for now. The registry comes from auth.NewRegistry, not from the extension. auth.NewExtension() registers a registry constructor that asks the container for a forge.Container, and nothing provides one, so resolving auth.Registry from the container fails today with "no provider for type di.Container". The Forge extension has the details.

The handler reads the subject with auth.GetAuthContext(ctx). The registry middleware stores it with ctx.Set, and what middleware stores that way reaches the handler.

Features

You need Forge Auth gives you
API key authentication in Go providers.NewAPIKeyProvider, reading a header (X-API-Key by default), a query parameter or a cookie
JWT or other Bearer tokens providers.NewBearerTokenProvider with your own token validator
HTTP Basic, OAuth 2.0 and OpenID Connect NewBasicAuthProvider, NewOAuth2Provider and NewOIDCProvider
LDAP or Active Directory sign-in providers.NewLDAPProvider, with StartTLS, a connection pool, a result cache and group-to-role mapping
More than one way to sign in Registry middleware that tries providers in order, or requires all of them
Role, scope and permission checks auth.Requirement and a pluggable auth.Authorizer
Documented security in your API spec forge.WithAuth, forge.WithAnyRole and forge.WithAllPermissions route metadata, read by the OpenAPI and client generators
Your own scheme Any type that implements auth.AuthProvider

Providers

Each provider takes a name, which is what you pass to middleware and route options, and a validator you write. Your validator gets the raw credential and returns an *auth.AuthContext, or an error such as auth.ErrInvalidCredentials. If you forget the validator, the provider returns auth.ErrInvalidConfiguration on every request.

Constructor Reads Options
NewAPIKeyProvider(name, ...) Header, then query, then cookie WithAPIKeyHeader, WithAPIKeyQuery, WithAPIKeyCookie, WithAPIKeyValidator, WithAPIKeyDescription, WithAPIKeyContainer
NewBearerTokenProvider(name, ...) Authorization: Bearer <token> WithBearerFormat, WithBearerValidator, WithBearerDescription, WithBearerContainer
NewBasicAuthProvider(name, ...) Authorization: Basic <base64> WithBasicAuthValidator, WithBasicAuthDescription, WithBasicAuthContainer
NewOAuth2Provider(name, flows, ...) A Bearer token WithOAuth2Validator, WithOAuth2Description, WithOAuth2Container
NewOIDCProvider(name, discoveryURL, ...) A Bearer token WithOIDCValidator, WithOIDCDescription, WithOIDCContainer
NewLDAPProvider(cfg, logger) HTTP Basic credentials, checked against your directory LDAPConfig, starting from DefaultLDAPConfig()

The Bearer, OAuth2 and OIDC providers don't verify tokens themselves. You check the signature, expiry and audience in your validator. The OAuth2 flows and OIDC discovery URL you pass only feed the OpenAPI security scheme. Here are a few registered side by side:

jwt := providers.NewBearerTokenProvider("jwt",
	providers.WithBearerFormat("JWT"),
	providers.WithBearerValidator(verifyJWT),
)
basic := providers.NewBasicAuthProvider("basic",
	providers.WithBasicAuthValidator(checkPassword),
)
oauth := providers.NewOAuth2Provider("oauth2", &auth.OAuthFlows{
	AuthorizationCode: &auth.OAuthFlow{
		AuthorizationURL: "https://id.example.com/authorize",
		TokenURL:         "https://id.example.com/token",
		Scopes:           map[string]string{"read": "Read access"},
	},
}, providers.WithOAuth2Validator(verifyJWT))

for _, p := range []auth.AuthProvider{jwt, basic, oauth} {
	if err := registry.Register(p); err != nil {
		return err
	}
}
LDAP and Active Directory
cfg := providers.DefaultLDAPConfig()
cfg.Host = "ldap.example.com"
cfg.BaseDN = "dc=example,dc=com"
cfg.BindDN = "cn=reader,dc=example,dc=com"
cfg.BindPassword = "from-your-secret-store"
cfg.RoleMapping = map[string]string{"cn=admins,ou=groups,dc=example,dc=com": "admin"}

ldapProvider, err := providers.NewLDAPProvider(cfg, logger)
if err != nil {
	return err
}

err = registry.Register(ldapProvider) // registered as "ldap"

The provider binds with your service account, searches with SearchFilter ((uid=%s) by default, (sAMAccountName=%s) for Active Directory), binds as the user to check the password, then looks up groups with GroupFilter. With an empty RoleMapping every group DN is passed through, and with a mapping only the mapped names are kept. Either way they land in the auth context's Scopes, not Roles, so check them with Requirement.Scopes. The attributes you list in Attributes become claims, and the user's DN and raw groups go in Metadata. Successful sign-ins are cached for CacheTTL (five minutes by default).

TLS is on by default. The connection starts plain on Port (389) and upgrades with StartTLS, verifying the certificate and hostname with TLS 1.2 or newer. Install your directory's certificate authority in the host trust store before you connect. InsecureSkipVerify is still in LDAPConfig so older config decodes, but setting it to true makes NewLDAPProvider return an error before any connection is attempted.

Writing your own provider

If your scheme isn't covered, implement auth.AuthProvider yourself: Name, Type, Authenticate(ctx, *http.Request), OpenAPIScheme and Middleware. The registry only calls Name and Authenticate on the request path, and OpenAPIScheme when you ask it for OpenAPISchemes(). You only need a real Middleware if you plan to use the provider on its own, outside the registry.

Protecting routes

The registry turns provider names into forge.Middleware. You attach it with forge.WithMiddleware on a route or forge.WithGroupMiddleware on a group.

Registry method Behaviour
Middleware(names...) Tries each provider in order and uses the first that succeeds. 401 when all fail
MiddlewareAnd(names...) Every provider must succeed. The first one's subject is kept, later claims and scopes are merged in
MiddlewareWithScopes(name, scopes...) One provider plus required scopes
MiddlewareWithRequirement(req) Authenticates with req.Providers (Mode: "and" for all-of), then calls the authorizer

A failed sign-in gets a plain-text 401 Unauthorized. A failed check gets 403 with the authorizer's error text. After a successful sign-in, the registry sets ProviderName on the auth context to the name the provider was registered under.

api := router.Group("/api",
	forge.WithGroupMiddleware(registry.Middleware("jwt", "api-key")),
	forge.WithGroupAuth("jwt", "api-key"),
)

deleteUser := registry.MiddlewareWithRequirement(auth.Requirement{
	Providers:   []string{"jwt"},
	Permissions: []string{"users:write", "users:delete"},
})

err := api.DELETE("/users/:id", handler,
	forge.WithMiddleware(deleteUser),
	forge.WithAllPermissions("users:write", "users:delete"),
)

You'll notice each rule appears twice. forge.WithAuth, forge.WithRequiredAuth, forge.WithAuthAnd, forge.WithAnyRole, forge.WithAllPermissions and their WithGroup... versions only record route metadata. The OpenAPI generator turns it into security requirements and an x-forge-authz extension, and the client generators read it too, but nothing is enforced until the registry middleware is on the route.

Reading the subject

auth.GetAuthContext(ctx) returns the *auth.AuthContext, in later middleware and in the handler alike. The registry doesn't put it in the request context, so auth.FromContext(ctx.Context()) finds it only if you copy it there with auth.WithContext, which code that takes a plain context.Context needs. MiddlewareWithRequirement also stores the subject's roles and scopes under auth.subject.roles and auth.subject.scopes for router interceptors.

AuthContext carries Subject, Claims, Scopes, Roles, Permissions, Metadata, Data and ProviderName, and has HasScope, HasScopes, HasRole, HasAnyRole, HasPermission, HasAllPermissions, GetClaim and GetClaimString.

Authorization

The default authorizer does plain set membership. The subject must hold all Scopes, any one of the Roles and all Permissions, and a requirement with none of the three passes. There's no role hierarchy and no wildcard matching. If you need real policy, you swap the authorizer:

type tenantAuthorizer struct{}

func (tenantAuthorizer) Name() string { return "tenant" }

func (tenantAuthorizer) Authorize(ctx context.Context, a *auth.AuthContext, req auth.Requirement) error {
	if tenant, _ := a.GetClaimString("tenant"); tenant == "" {
		return fmt.Errorf("%w: no tenant claim", auth.ErrAuthorizationFailed)
	}

	return auth.NewDefaultAuthorizer().Authorize(ctx, a, req)
}

registry.SetAuthorizer(tenantAuthorizer{})

Return nil to allow. Any error becomes a 403 with the error's text. Wrap auth.ErrAuthorizationFailed or auth.ErrInsufficientScopes so callers can tell the cases apart with errors.Is. SetAuthorizer(nil) is ignored, so the registry always has one. Warden is built to plug in here.

Credentials from protocol frames

For transports that receive a token after the HTTP handshake (a WebSocket or SSE control frame, for example), auth.ParseExplicitFrameCredential parses a bare token, Bearer <token> or DPoP <token>. Trusted transport code can then mark it with auth.WithExplicitFrameCredential, and a provider can check the mark with auth.MatchesExplicitFrameCredential. The mark stores only a SHA-256 digest and grants no identity: your provider still validates the token.

The Forge extension

auth.NewExtension(opts...) is the extension entry point:

Option Sets
WithEnabled(bool) false skips registration
WithAuthorizer(auth.Authorizer) The authorizer the registry starts with
WithProviders(...ProviderConfig), WithDefaultProvider(string) Stored on Config, but Register doesn't read them yet
WithRequireConfig(bool), WithConfig(auth.Config) The rest of Config. NewExtensionWithConfig(cfg) is NewExtension(WithConfig(cfg))

The extension also reads extensions.auth (falling back to auth) from your config file, with snake_case keys: enabled, default_provider and providers. Your options win over the file. The authorizer has no YAML form. When enabled, Register adds an auth.Registry constructor under the names auth:registry and auth.Registry, and auth.GetRegistry, GetRegistryFromApp and their Must versions resolve it. As the quick start explains, that constructor currently fails because it needs a forge.Container from the container. Until it changes, build the registry with auth.NewRegistry(app.Container(), app.Logger()) and keep a reference to it. One knock-on effect: the OpenAPI generator's lookup of auth:registry finds nothing, so security scheme definitions aren't added to components automatically, even though each operation still gets its security requirements.

Errors

Error Meaning
ErrMissingCredentials The request had no credential the provider recognised
ErrInvalidCredentials The credential was malformed or your validator rejected it
ErrInvalidConfiguration A provider without a validator, or a provider with an empty name
ErrProviderExists, ErrProviderNotFound Register with a duplicate name, or Get/Unregister with an unknown one
ErrInsufficientScopes, ErrAuthorizationFailed Returned by authorizers
ErrAuthenticationFailed, ErrTokenExpired, ErrTokenInvalid For your validators to return

Documentation

Topic Where
Features and usage Auth features
Configuration reference Auth configuration
Function reference Auth functions
Go API pkg.go.dev and the providers package
Route metadata and OpenAPI output Forge OpenAPI guide

The docs pages and older examples resolve the registry from the container and read the auth context from the request in the handler. Neither works as written today, for the two reasons in the quick start.

Examples

examples/auth_example registers API key, JWT, Basic and OAuth2 providers and builds OpenAPI docs for them. It predates the two issues above, so it won't run unmodified: build the registry with auth.NewRegistry and attach registry.MiddlewareWithRequirement(...) to your routes, as the quick start does.

FAQ

How do I add API key authentication to a Go REST API?

Register providers.NewAPIKeyProvider("api-key", providers.WithAPIKeyValidator(yourCheck)) with a registry, then put registry.Middleware("api-key") on the routes you want to protect. The quick start is a complete program.

Does Forge Auth validate JWTs for me?

No. The Bearer provider pulls the token out of the Authorization header and passes it to your validator. Use the JWT library you trust to check the signature, expiry and audience there, and return the subject, roles and scopes.

Can a route accept either an API key or a JWT?

Yes. registry.Middleware("api-key", "jwt") tries them in order and lets the request through on the first success. MiddlewareAnd does the opposite and requires both.

How do I check roles and permissions per route?

Use registry.MiddlewareWithRequirement with Roles (any one is enough) or Permissions (all are required). Add forge.WithAnyRole or forge.WithAllPermissions too if you want the rule in your OpenAPI document.

Does it support LDAP or Active Directory?

Yes, through providers.NewLDAPProvider. Clients send HTTP Basic credentials, the provider checks them against your directory over StartTLS, and RoleMapping turns directory groups into the scope strings your routes check.

Contributing

Auth is its own Go module in the Forge repository. Build and test it from its directory the way CI does:

cd extensions/auth
GOWORK=off go build ./...
GOWORK=off go vet ./...
GOWORK=off go test -short -count=1 ./...
golangci-lint run ./...

AGENTS.md has the pinned lint version and the repository conventions. Commits follow Conventional Commits, for example fix(auth): ....

License

Forge Auth is part of Forge and is available under the Apache License 2.0.

Documentation

Index

Constants

View Source
const (
	// RegistryKey is the DI key for the auth registry.
	RegistryKey = "auth:registry"
	// RegistryKeyLegacy is the legacy DI key for the auth registry.
	RegistryKeyLegacy = "auth.Registry"
)

DI container keys for auth extension services.

Variables

View Source
var (
	// ErrProviderNotFound is returned when a provider is not found.
	ErrProviderNotFound = errors.New("auth provider not found")

	// ErrProviderExists is returned when a provider already exists.
	ErrProviderExists = errors.New("auth provider already exists")

	// ErrInvalidConfiguration is returned when configuration is invalid.
	ErrInvalidConfiguration = errors.New("invalid auth configuration")

	// ErrMissingCredentials is returned when credentials are missing.
	ErrMissingCredentials = errors.New("missing authentication credentials")

	// ErrInvalidCredentials is returned when credentials are invalid.
	ErrInvalidCredentials = errors.New("invalid credentials")

	// ErrInsufficientScopes is returned when required scopes are missing.
	ErrInsufficientScopes = errors.New("insufficient scopes")

	// ErrAuthenticationFailed is returned when authentication fails.
	ErrAuthenticationFailed = errors.New("authentication failed")

	// ErrAuthorizationFailed is returned when authorization fails.
	ErrAuthorizationFailed = errors.New("authorization failed")

	// ErrTokenExpired is returned when a token has expired.
	ErrTokenExpired = errors.New("token expired")

	// ErrTokenInvalid is returned when a token is invalid.
	ErrTokenInvalid = errors.New("invalid token")
)

Functions

func MatchesExplicitFrameCredential added in v1.12.5

func MatchesExplicitFrameCredential(ctx context.Context, scheme, token string) bool

MatchesExplicitFrameCredential reports whether trusted transport code marked this exact scheme and token as an explicit frame presentation. Only check credentials extracted from a valid Authorization presentation. A match does not authenticate the token or promote credentials read from cookie fallback.

func NewExtension

func NewExtension(opts ...ConfigOption) forge.Extension

NewExtension creates a new auth extension with functional options.

Example:

auth.NewExtension(
    auth.WithEnabled(true),
    auth.WithDefaultProvider("jwt"),
)

func NewExtensionWithConfig

func NewExtensionWithConfig(config Config) forge.Extension

NewExtensionWithConfig creates a new auth extension with a complete config. This is for backward compatibility or when config is fully known at initialization.

func ParseExplicitFrameCredential added in v1.12.5

func ParseExplicitFrameCredential(presentation string) (scheme, token string, err error)

ParseExplicitFrameCredential parses a protocol frame's selected credential. You can supply a bare token, Bearer <token>, or DPoP <token>. The returned scheme is canonical, and token bytes are preserved after splitting fields. Proofs must use the transport's existing proof channel.

func WithContext

func WithContext(ctx context.Context, authCtx *AuthContext) context.Context

WithContext adds the AuthContext to a context.

func WithExplicitFrameCredential added in v1.12.5

func WithExplicitFrameCredential(ctx context.Context, scheme, token string) (context.Context, error)

WithExplicitFrameCredential records a credential received in a protocol frame after request middleware. Call it only from trusted transport code, using the same parsed scheme and token you put in the Authorization header. Never create this marker from request headers, cookies, or claims.

The marker stores a digest, preserves the parent context, and grants no identity. Providers must still validate the credential and any required proof.

Types

type AuthContext

type AuthContext struct {
	// Subject is the authenticated entity (user ID, service ID, etc.)
	Subject string

	// Claims holds additional authentication claims (roles, permissions, etc.)
	Claims map[string]any

	// Scopes holds OAuth2 scopes or permission strings
	Scopes []string

	// Roles holds the authenticated subject's roles (e.g. "admin", "editor").
	// Route requirements treat roles as any-of.
	Roles []string

	// Permissions holds fine-grained permission strings
	// (e.g. "users:write"). Route requirements treat permissions as all-of.
	Permissions []string

	// Metadata holds provider-specific metadata
	Metadata map[string]any

	// Data holds additional data from the authenticated provider
	Data any

	// ProviderName identifies which auth provider authenticated this request
	ProviderName string
}

AuthContext holds authenticated user/service information. This is stored in the request context after successful authentication.

func FromContext

func FromContext(ctx context.Context) (*AuthContext, bool)

FromContext retrieves the AuthContext from the request context. Returns the auth context and true if found, nil and false otherwise.

func GetAuthContext

func GetAuthContext(ctx forge.Context) (*AuthContext, bool)

GetAuthContext retrieves the AuthContext from forge.Context. Returns the auth context and true if found, nil and false otherwise.

func GetAuthContextFromStdContext

func GetAuthContextFromStdContext(ctx context.Context) (*AuthContext, bool)

GetAuthContextFromStdContext retrieves AuthContext from standard context.Context. This is for backward compatibility with code that uses standard context. For new code using forge.Context, use GetAuthContext instead.

func MustFromContext

func MustFromContext(ctx context.Context) *AuthContext

MustFromContext retrieves the AuthContext or panics. Use this only when you're certain the context contains auth information (e.g., after auth middleware has run).

func MustGetAuthContext

func MustGetAuthContext(ctx forge.Context) *AuthContext

MustGetAuthContext retrieves the AuthContext from forge.Context or panics. Use this only when you're certain the context contains auth information (e.g., after auth middleware has run).

func (*AuthContext) GetClaim

func (a *AuthContext) GetClaim(key string) (any, bool)

GetClaim retrieves a claim by key.

func (*AuthContext) GetClaimString

func (a *AuthContext) GetClaimString(key string) (string, bool)

GetClaimString retrieves a string claim.

func (*AuthContext) HasAllPermissions added in v1.9.8

func (a *AuthContext) HasAllPermissions(permissions ...string) bool

HasAllPermissions reports whether the subject holds every permission.

func (*AuthContext) HasAnyRole added in v1.9.8

func (a *AuthContext) HasAnyRole(roles ...string) bool

HasAnyRole reports whether the subject holds at least one of the roles.

No roles required means everyone satisfies it. A requirement that demands nothing must not deny, or threading an unguarded route through a generic authorizer would start refusing it.

func (*AuthContext) HasPermission added in v1.9.8

func (a *AuthContext) HasPermission(permission string) bool

HasPermission reports whether the subject holds a permission.

func (*AuthContext) HasRole added in v1.9.8

func (a *AuthContext) HasRole(role string) bool

HasRole reports whether the subject holds a role.

func (*AuthContext) HasScope

func (a *AuthContext) HasScope(scope string) bool

HasScope checks if the auth context has a specific scope.

func (*AuthContext) HasScopes

func (a *AuthContext) HasScopes(scopes ...string) bool

HasScopes checks if the auth context has all specified scopes.

type AuthProvider

type AuthProvider interface {
	// Name returns the unique name/ID of this auth provider.
	// This name is used to reference the provider in route/group options.
	Name() string

	// Type returns the OpenAPI security scheme type
	Type() SecuritySchemeType

	// Authenticate validates the request and returns the authenticated subject.
	// Returns the auth context (user, claims, etc.) or an error.
	// The provider can access services from the DI container via closures.
	Authenticate(ctx context.Context, r *http.Request) (*AuthContext, error)

	// OpenAPIScheme returns the OpenAPI security scheme definition.
	// This is used to automatically generate OpenAPI security documentation.
	OpenAPIScheme() SecurityScheme

	// Middleware returns HTTP middleware for this provider.
	// The middleware is automatically applied when the provider is used.
	Middleware() forge.Middleware
}

AuthProvider defines the interface for authentication providers. Implementations can access the DI container through closures or context to retrieve services needed for authentication (database, cache, etc.).

type Authorizer added in v1.9.8

type Authorizer interface {
	Name() string
	Authorize(ctx context.Context, authCtx *AuthContext, req Requirement) error
}

Authorizer decides whether an authenticated subject satisfies a route's declared requirement.

Forge ships one implementation doing plain set membership. warden replaces it wholesale through Registry.SetAuthorizer. Forge itself never grows role hierarchies, permission inheritance or wildcard matching: that is policy, and policy belongs to the authorizer.

Authorize returns an error rather than a bool so an implementation can say why it refused. A nil return allows. An error wrapping ErrAuthorizationFailed or ErrInsufficientScopes maps to 403.

func NewDefaultAuthorizer added in v1.9.8

func NewDefaultAuthorizer() Authorizer

NewDefaultAuthorizer returns the built-in set-membership authorizer.

type Config

type Config struct {
	// Enabled determines if auth extension is enabled
	Enabled bool `yaml:"enabled"`

	// Providers holds provider-specific configurations
	Providers []ProviderConfig `yaml:"providers"`

	// DefaultProvider is the default provider name to use
	DefaultProvider string `yaml:"default_provider"`

	// RequireConfig requires config from ConfigManager
	RequireConfig bool `yaml:"-"`

	// Authorizer, when set, replaces the built-in set-membership authorizer.
	// This is the seam a policy engine such as warden plugs into.
	Authorizer Authorizer `yaml:"-"`
}

Config holds auth extension configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default auth configuration.

type ConfigOption

type ConfigOption func(*Config)

ConfigOption configures the auth extension.

func WithAuthorizer added in v1.9.8

func WithAuthorizer(a Authorizer) ConfigOption

WithAuthorizer sets the authorization decision maker.

Example:

auth.NewExtension(
    auth.WithAuthorizer(warden.New(policySource)),
)

func WithConfig

func WithConfig(config Config) ConfigOption

WithConfig sets the complete config.

func WithDefaultProvider

func WithDefaultProvider(name string) ConfigOption

WithDefaultProvider sets the default provider.

func WithEnabled

func WithEnabled(enabled bool) ConfigOption

WithEnabled sets whether auth is enabled.

func WithProviders

func WithProviders(providers ...ProviderConfig) ConfigOption

WithProviders sets the provider configurations.

func WithRequireConfig

func WithRequireConfig(require bool) ConfigOption

WithRequireConfig requires config from ConfigManager.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension implements forge.Extension for authentication and authorization. It provides a registry for managing auth providers and automatically integrates with the router for OpenAPI security scheme generation. The extension is now a lightweight facade that loads config and registers services.

func (*Extension) Health

func (e *Extension) Health(ctx context.Context) error

Health checks the extension health.

func (*Extension) Register

func (e *Extension) Register(app forge.App) error

Register registers the auth extension with the app. This method now only loads configuration and registers service constructors.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start marks the extension as started.

func (*Extension) Stop

func (e *Extension) Stop(ctx context.Context) error

Stop marks the extension as stopped.

type OAuthFlow

type OAuthFlow = shared.OAuthFlow

OAuthFlow defines a single OAuth 2.0 flow.

type OAuthFlows

type OAuthFlows = shared.OAuthFlows

OAuthFlows defines OAuth 2.0 flows.

type ProviderConfig

type ProviderConfig struct {
	Name        string         `yaml:"name"`
	Type        string         `yaml:"type"`
	Enabled     bool           `yaml:"enabled"`
	Description string         `yaml:"description"`
	Config      map[string]any `yaml:"config"`
}

ProviderConfig holds configuration for a single auth provider.

type ProviderFunc

type ProviderFunc func(ctx context.Context, r *http.Request) (*AuthContext, error)

ProviderFunc is a function adapter for simple auth providers.

type Registry

type Registry interface {
	// Register registers an auth provider
	Register(provider AuthProvider) error

	// Unregister removes a provider by name
	Unregister(name string) error

	// Get retrieves a provider by name
	Get(name string) (AuthProvider, error)

	// Has checks if a provider exists
	Has(name string) bool

	// List returns all registered provider names
	List() []string

	// Middleware creates combined middleware for multiple providers.
	// When multiple providers are specified, they are tried in order (OR logic).
	// Authentication succeeds if ANY provider succeeds.
	Middleware(providerNames ...string) forge.Middleware

	// MiddlewareAnd creates middleware requiring ALL providers to succeed (AND logic).
	MiddlewareAnd(providerNames ...string) forge.Middleware

	// MiddlewareWithScopes creates middleware with required scopes
	MiddlewareWithScopes(providerName string, scopes ...string) forge.Middleware

	// MiddlewareWithRequirement authenticates through the requirement's
	// providers and then hands the result to the authorizer.
	MiddlewareWithRequirement(req Requirement) forge.Middleware

	// OpenAPISchemes returns all security schemes for OpenAPI generation
	OpenAPISchemes() map[string]SecurityScheme

	// SetAuthorizer replaces the authorization decision maker. Passing nil is
	// ignored, so a misconfigured caller cannot leave the registry without one.
	SetAuthorizer(a Authorizer)

	// Authorizer returns the current decision maker. Never nil.
	Authorizer() Authorizer
}

Registry manages authentication providers. It provides thread-safe registration and retrieval of auth providers, and can create middleware that chains multiple providers.

func GetRegistry

func GetRegistry(c forge.Container) (Registry, error)

GetRegistry retrieves the auth Registry from the container. Returns error if not found or type assertion fails.

func GetRegistryFromApp

func GetRegistryFromApp(app forge.App) (Registry, error)

GetRegistryFromApp retrieves the auth Registry from the app. Returns error if not found or type assertion fails.

func MustGetRegistry

func MustGetRegistry(c forge.Container) Registry

MustGetRegistry retrieves the auth Registry from the container. Panics if not found or type assertion fails.

func MustGetRegistryFromApp

func MustGetRegistryFromApp(app forge.App) Registry

MustGetRegistryFromApp retrieves the auth Registry from the app. Panics if not found or type assertion fails.

func NewRegistry

func NewRegistry(container forge.Container, logger forge.Logger) Registry

NewRegistry creates a new auth provider registry.

type Requirement added in v1.9.8

type Requirement struct {
	// Providers are the auth providers that may satisfy authentication.
	Providers []string

	// Scopes must all be held. Existing WithRequiredAuth behaviour.
	Scopes []string

	// Roles: holding any one of them satisfies the requirement.
	Roles []string

	// Permissions must all be held.
	Permissions []string

	// Mode is "or" or "and", and applies to Providers only.
	Mode string
}

Requirement is the static authorization requirement a route declares.

It is a value rather than a closure on purpose: a value can be written into an OpenAPI document and read back into a generated client, and a closure cannot. That is the constraint that keeps v1 free of resource-scoped checks (can this user edit document 42), which stay inside handlers.

func (Requirement) IsEmpty added in v1.9.8

func (r Requirement) IsEmpty() bool

IsEmpty reports whether this requirement asks anything of the subject beyond being authenticated.

Providers are excluded: naming a provider is an authentication requirement, which the middleware has already enforced by the time an authorizer runs.

type SecurityScheme

type SecurityScheme = shared.SecurityScheme

SecurityScheme represents an OpenAPI security scheme definition We use the shared type to ensure compatibility with OpenAPI generation.

type SecuritySchemeType

type SecuritySchemeType string

SecuritySchemeType represents OpenAPI 3.1 security scheme types.

const (
	SecurityTypeAPIKey        SecuritySchemeType = "apiKey"
	SecurityTypeHTTP          SecuritySchemeType = "http"
	SecurityTypeOAuth2        SecuritySchemeType = "oauth2"
	SecurityTypeOpenIDConnect SecuritySchemeType = "openIdConnect"
	SecurityTypeMutualTLS     SecuritySchemeType = "mutualTLS"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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