Package bearer

import "funnyhaha.technology/httpkit/bearer"
View the source code

Overview

Package bearer protects an API as an OAuth resource server: it takes bearer access tokens (RFC 6750), verifies them with an operation, and answers a request without a good token with the challenge that tells a client where to sign in (RFC 9728). It also serves the protected resource metadata that challenge points to.

It doesn't verify tokens itself. A VerifyTokenOpFunc does, such as the one funnyhaha.technology/httpkit/jwks builds, or a stub in tests:

challenge, metadataURL, err := bearer.NewChallenge("mcp", resource)
protect := bearer.New(verify, challenge).WithScope("public:read").Middleware()
mux.Handle("GET "+metadataPath, bearer.NewMetadata(metadata).Handler())
mux.Handle("/mcp", protect(mcpHandler))

A handler behind the middleware reads the token's claims with ClaimsFrom.

Index

Constants

const (
	// DefaultRetryAfter is the Retry-After sent with a 503 when the keys
	// that verify tokens can't be fetched.
	DefaultRetryAfter = 30 * time.Second

	// DefaultMetadataMaxAge is how long clients may cache the protected
	// resource metadata.
	DefaultMetadataMaxAge = time.Hour
)
source

Defaults, used unless a With method changes them.

Variables

var (
	// ErrInvalidToken is returned by a VerifyTokenOpFunc for a token that's
	// expired, revoked, malformed, not for this resource or otherwise
	// unacceptable. The middleware answers it with 401 and invalid_token.
	ErrInvalidToken = errors.New("invalid token")

	// ErrUnavailable is returned by a VerifyTokenOpFunc that can't decide,
	// because what it verifies with, such as the authorization server's
	// keys, can't be fetched. The middleware answers it with 503.
	ErrUnavailable = errors.New("token verification unavailable")

	// ErrNoClaims is returned by [SubjectKey] for a request without a
	// verified token's subject.
	ErrNoClaims = errors.New("no verified token subject")
)
source

Functions

ClaimsFrom source

func ClaimsFrom(ctx context.Context) (rfc9068.Claims, bool)

ClaimsFrom returns the claims of the request's verified token, and false if the request didn't pass through the middleware.

MetadataPath source

func MetadataPath(metadataURL string) (string, error)

MetadataPath returns the path of a metadata URL, such as "/.well-known/oauth-protected-resource/mcp", for routing.

NewChallenge source

func NewChallenge(realm, resource string) (rfc6750.Challenge, string, error)

NewChallenge returns the Bearer challenge for a resource, with the realm and a resource_metadata parameter pointing at the resource's protected resource metadata, and that metadata's URL (RFC 9728, Sections 3.1 and 5.1). Serve the metadata at the URL's path with NewMetadata.

SubjectKey source

func SubjectKey(r *http.Request) (string, error)

SubjectKey returns the sub claim of the request's verified token, for rate limiting per subject (funnyhaha.technology/httpkit/ratelimit) behind the middleware. It returns ErrNoClaims for a request that didn't pass through the middleware, rather than counting every request under one key.

WithClaims source

func WithClaims(ctx context.Context, c rfc9068.Claims) context.Context

WithClaims returns a copy of ctx carrying a verified token's claims. The middleware calls it; tests of handlers behind it can too.

Types

Builder source

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

Builder configures the middleware. Its With methods return a modified copy.

New source

func New(verify VerifyTokenOpFunc, challenge rfc6750.Challenge) Builder

New returns a builder for middleware that lets a request through only with a bearer token that verify accepts. The challenge is sent with every 401 and 403, so build it with NewChallenge to point clients at the metadata. It panics if verify is nil.

(Builder) Middleware source

func (b Builder) Middleware() func(http.Handler) http.Handler

Middleware returns the middleware. A request

Each error response is a problem (RFC 9457). The reason a token was refused goes to the request log, as auth_refused, and the token never does. A request with a good token proceeds, with its claims in the context for ClaimsFrom.

(Builder) WithRetryAfter source

func (b Builder) WithRetryAfter(d time.Duration) Builder

WithRetryAfter returns a copy of b that sends Retry-After: d with a 503.

(Builder) WithScope source

func (b Builder) WithScope(s rfc6750.Scope, more ...rfc6750.Scope) Builder

WithScope returns a copy of b that requires every request's token to have the scopes, as well as any required already. A token without one gets 403 with insufficient_scope, and every challenge lists the scopes, so a client knows what to ask for.

MetadataBuilder source

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

MetadataBuilder configures the protected resource metadata handler. Its With methods return a modified copy.

NewMetadata source

func NewMetadata(m rfc9728.Metadata) MetadataBuilder

NewMetadata returns a builder for a handler that serves m as protected resource metadata (RFC 9728, Section 3.2). It panics if m has no Resource, which Section 2 requires.

(MetadataBuilder) Handler source

func (b MetadataBuilder) Handler() http.Handler

Handler returns the handler. The document is marshalled once, here, so it panics if the metadata can't be. Cross-origin requests are the caller's to allow, with CORS middleware.

(MetadataBuilder) WithMaxAge source

func (b MetadataBuilder) WithMaxAge(d time.Duration) MetadataBuilder

WithMaxAge returns a copy of b whose responses clients may cache for d.

VerifyTokenOpFunc source

type VerifyTokenOpFunc func(context.Context, VerifyTokenParams) (VerifyTokenResult, error)

VerifyTokenOpFunc verifies an access token and returns its claims. It returns an error wrapping ErrInvalidToken for a token that mustn't be accepted, and ErrUnavailable when it can't tell.

VerifyTokenParams source

type VerifyTokenParams struct {
	Token rfc6750.Token
}

VerifyTokenParams holds the token to verify.

VerifyTokenResult source

type VerifyTokenResult struct {
	Claims rfc9068.Claims
}

VerifyTokenResult holds a verified token's claims.