authz

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

authz — generic, scope-based authorization

A small, generic authorization layer for HTTP services. It authorizes from the OAuth2 scopes carried on the token and is fully decoupled from the authentication layer.

Design principles

  • Imports nothing internal. The package depends only on the standard library. It defines a minimal Principal interface — Subject(), Roles(), Scopes() — which the authn layer's *auth.AuthContext satisfies structurally. authz never imports internal/auth.
  • Principal via dependency injection. An Extractor func(context.Context) (Principal, bool) is supplied at construction. The composition root wires the authn lookup into it, so this package has no knowledge of how authentication works.
  • Scopes come from the token. Decisions use the scopes the IdP granted on the token (e.g. nsw:consignment:read); there is no role- or client-ID-to-scope mapping. Roles() is exposed for finer, business-level checks at the service layer.
  • App scopes stay in the app. Concrete scope strings (nsw:...) are defined at the composition root, never in this generic package.

API

type Principal interface { Subject() string; Roles() []string; Scopes() []string }
type Extractor func(context.Context) (Principal, bool)

func New(extract Extractor) (*Authorizer, error)

func (a *Authorizer) Principal(ctx context.Context) (Principal, bool)
func (a *Authorizer) RequireScope(scope string) func(http.Handler) http.Handler
func (a *Authorizer) RequireAnyScope(scopes ...string) func(http.Handler) http.Handler
func (a *Authorizer) RequireAllScopes(scopes ...string) func(http.Handler) http.Handler

func HasScope(p Principal, scope string) bool
func HasAnyScope(p Principal, scopes ...string) bool
func HasAllScopes(p Principal, scopes ...string) bool

var ErrUnauthenticated, ErrForbidden error

Middleware returns 401 when the request is unauthenticated and 403 when the principal lacks the required scope(s).

Wiring (composition root)

authzr, err := authz.New(func(ctx context.Context) (authz.Principal, bool) {
    ac := auth.GetAuthContext(ctx)
    if ac == nil || ac.Type() == "" {
        return nil, false
    }
    return ac, true // *auth.AuthContext satisfies authz.Principal structurally
})
if err != nil {
    return fmt.Errorf("init authz: %w", err)
}

// Apply per route, after the authn middleware. Scope constants are app-defined.
mux.Handle("GET /api/v1/consignments",
    withAuth(authzr.RequireScope(scopes.ConsignmentRead)(
        http.HandlerFunc(consignmentRouter.HandleGetConsignments))))

Service-layer checks

For logic beyond a coarse route gate, resolve the principal and inspect it:

p, ok := authzr.Principal(ctx)
if !ok {
    return authz.ErrUnauthenticated
}
if !authz.HasScope(p, scopes.ConsignmentWrite) {
    return authz.ErrForbidden
}
// p.Roles() / p.Subject() are available for finer, business-specific decisions.

Documentation

Overview

Package authz provides generic, transport-agnostic authorization for HTTP services, driven by the OAuth2 scopes carried on a request's authenticated principal.

It is intentionally decoupled from any authentication implementation: it defines a minimal Principal interface (Subject/Roles/Scopes) and receives the principal through an injected Extractor, so it imports nothing from the authn layer (or any other internal package). In this codebase *auth.AuthContext satisfies Principal structurally; the composition root wires auth.GetAuthContext into the Extractor.

Authorization decisions use the scopes already granted on the token (for example "nsw:consignment:read"). Application-specific scope strings are defined by the caller, never here.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnauthenticated = errors.New("authz: unauthenticated")
	ErrForbidden       = errors.New("authz: forbidden")
)

Sentinel errors for callers performing service-layer authorization. The HTTP middleware in this package translates these conditions into 401/403 itself.

Functions

func HasAllScopes

func HasAllScopes(p Principal, scopes ...string) bool

HasAllScopes reports whether the principal holds every one of the scopes. With no scopes provided it returns true (vacuously satisfied).

func HasAnyScope

func HasAnyScope(p Principal, scopes ...string) bool

HasAnyScope reports whether the principal holds at least one of the scopes. With no scopes provided it returns false.

func HasScope

func HasScope(p Principal, scope string) bool

HasScope reports whether the principal was granted the exact scope.

Types

type Authorizer

type Authorizer struct {
	// contains filtered or unexported fields
}

Authorizer is the policy enforcement point. It resolves the authenticated Principal (via the injected Extractor) and provides middleware that gates HTTP routes on OAuth2 scopes.

func New

func New(extract Extractor) (*Authorizer, error)

New constructs an Authorizer. extract supplies the authenticated Principal from a request context (typically wrapping the authn layer's context lookup). It returns an error if extract is nil.

func (*Authorizer) Principal

func (a *Authorizer) Principal(ctx context.Context) (Principal, bool)

Principal returns the authenticated principal from ctx, or (nil, false) when the request is unauthenticated. Useful for service/handler-layer checks that go beyond a coarse scope gate.

func (*Authorizer) RequireAllScopes

func (a *Authorizer) RequireAllScopes(scopes ...string) func(http.Handler) http.Handler

RequireAllScopes admits the request only if the principal holds every one of scopes.

func (*Authorizer) RequireAnyScope

func (a *Authorizer) RequireAnyScope(scopes ...string) func(http.Handler) http.Handler

RequireAnyScope admits the request if the principal holds at least one of scopes.

func (*Authorizer) RequireScope

func (a *Authorizer) RequireScope(scope string) func(http.Handler) http.Handler

RequireScope returns middleware that admits a request only if the principal holds scope: 401 when unauthenticated, 403 when authenticated but missing it.

type Extractor

type Extractor func(ctx context.Context) (Principal, bool)

Extractor retrieves the authenticated Principal from a request context. It is injected at construction so this package stays decoupled from any specific authentication implementation. It must return (nil, false) when the request is unauthenticated.

type Principal

type Principal interface {
	// Subject returns a stable identifier for the principal: a user ID for user
	// principals or a machine client ID for client (M2M) principals.
	Subject() string
	// Roles returns the roles granted to the principal, or nil.
	Roles() []string
	// Scopes returns the OAuth2 scopes granted to the principal, or nil.
	Scopes() []string
}

Principal is the minimal, transport-agnostic identity contract this package needs to make authorization decisions. It is deliberately tiny so an authentication layer's context type satisfies it *structurally* — in this codebase *auth.AuthContext already implements Subject/Roles/Scopes — which is why this package imports nothing from the authn layer. Authorization is driven by the OAuth2 scopes granted on the token; Roles is exposed for callers that need finer, business-level checks at the service layer.

Jump to

Keyboard shortcuts

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