handlers

package
v0.34.0 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package handlers provides HTTP handlers for the OAuth 2.0 authorization server endpoints.

This package implements the HTTP layer for the authorization server, including:

  • OIDC Discovery endpoint (/.well-known/openid-configuration)
  • JWKS endpoint (/.well-known/jwks.json)
  • OAuth endpoints (authorize, token, callback, register) - to be implemented

The Handler struct coordinates all handlers and provides route registration methods for integrating with standard Go HTTP servers.

Index

Constants

View Source
const (
	// DefaultJWKSCacheMaxAge is the Cache-Control max-age for the JWKS endpoint (1 hour).
	// This balances caching efficiency with timely key rotation propagation.
	DefaultJWKSCacheMaxAge = 3600

	// DefaultDiscoveryCacheMaxAge is the Cache-Control max-age for the discovery endpoint (1 hour).
	// Aligned with Google's OIDC discovery cache policy.
	DefaultDiscoveryCacheMaxAge = 3600
)

Cache-Control max-age values for discovery endpoints. These are not exposed to users but extracted as constants for documentation and maintainability.

View Source
const MaxDCRBodySize = 64 * 1024

MaxDCRBodySize is the maximum allowed size for DCR request bodies (64KB). This prevents DoS attacks via extremely large payloads while being generous enough for legitimate requests with multiple redirect URIs.

It is exported to serve as the single source of truth for the auth-server body-size cap: the embedded auth server (pkg/authserver/runner) derives its own request-body limit from this constant so the two cannot drift.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

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

Handler provides HTTP handlers for the OAuth authorization server endpoints.

func NewHandler

func NewHandler(
	provider fosite.OAuth2Provider,
	config *server.AuthorizationServerConfig,
	stor storage.Storage,
	upstreams []NamedUpstream,
	opts ...Option,
) (*Handler, error)

NewHandler creates a new Handler with the given dependencies. upstreams defines the ordered sequence of upstream providers consulted during multi-upstream authorization flows (e.g., sequential token acquisition).

Returns an error if config is nil, if config's embedded *fosite.Config is nil, if upstreams is empty, or if any entry has an empty name, a nil provider, or a duplicate name. Upstream names must be unique: upstreamByName returns the first match, tokens are keyed by name, and the authorization chain is keyed by name — a duplicate would silently shadow a provider. Catching misconfiguration here is far easier to diagnose than a nil-deref panic or a shadowed provider deep inside an HTTP handler at request time.

func (*Handler) AuthorizeHandler added in v0.8.1

func (h *Handler) AuthorizeHandler(w http.ResponseWriter, req *http.Request)

AuthorizeHandler handles GET /oauth/authorize requests. It validates the client's authorization request and redirects to the upstream IDP.

func (*Handler) CallbackHandler added in v0.8.1

func (h *Handler) CallbackHandler(w http.ResponseWriter, req *http.Request)

CallbackHandler handles GET /oauth/callback requests. It exchanges the upstream authorization code and issues our own authorization code.

func (*Handler) JWKSHandler

func (h *Handler) JWKSHandler(w http.ResponseWriter, _ *http.Request)

JWKSHandler handles GET /.well-known/jwks.json requests. It returns the public keys used for verifying JWTs.

func (*Handler) OAuthDiscoveryHandler

func (h *Handler) OAuthDiscoveryHandler(w http.ResponseWriter, _ *http.Request)

OAuthDiscoveryHandler handles GET /.well-known/oauth-authorization-server requests. It returns the OAuth 2.0 Authorization Server Metadata per RFC 8414. This endpoint is useful for non-OIDC OAuth clients.

func (*Handler) OAuthRoutes

func (h *Handler) OAuthRoutes(r chi.Router)

OAuthRoutes registers OAuth endpoints (authorize, callback, token, register) on the provided router.

func (*Handler) OIDCDiscoveryHandler

func (h *Handler) OIDCDiscoveryHandler(w http.ResponseWriter, _ *http.Request)

OIDCDiscoveryHandler handles GET /.well-known/openid-configuration requests. It returns the OIDC discovery document describing the authorization server capabilities. This extends the OAuth 2.0 AS Metadata (RFC 8414) with OIDC-specific fields.

func (*Handler) RegisterClientHandler added in v0.8.1

func (h *Handler) RegisterClientHandler(w http.ResponseWriter, req *http.Request)

RegisterClientHandler handles POST /oauth/register requests. It implements RFC 7591 Dynamic Client Registration for public clients with loopback redirect URIs only.

func (*Handler) Routes

func (h *Handler) Routes() http.Handler

Routes returns a router with all OAuth/OIDC endpoints registered.

func (*Handler) TokenHandler added in v0.8.1

func (h *Handler) TokenHandler(w http.ResponseWriter, req *http.Request)

TokenHandler handles POST /oauth/token requests. It processes token requests using fosite's access request/response flow.

func (*Handler) WellKnownRoutes

func (h *Handler) WellKnownRoutes(r chi.Router)

WellKnownRoutes registers well-known endpoints (JWKS, OAuth/OIDC discovery) on the provided router. Both discovery endpoints are registered per the MCP specification requirement to provide at least one discovery mechanism, with both supported for maximum interoperability: - /.well-known/oauth-authorization-server (RFC 8414) for OAuth-only clients - /.well-known/openid-configuration (OIDC Discovery 1.0) for OIDC clients

The wildcard variants (/.well-known/oauth-authorization-server/*) handle RFC 8414 Section 3.1 path-based issuers, where clients insert /.well-known/ before the issuer's path component (e.g., /.well-known/oauth-authorization-server/inject-test for issuer https://example.com/inject-test).

type NamedUpstream added in v0.12.5

type NamedUpstream struct {
	Name     string
	Provider upstream.OAuth2Provider
}

NamedUpstream pairs a logical provider name with its OAuth2Provider implementation. The name is used as the storage key and must be unique within the upstream slice.

type Option added in v0.31.0

type Option func(*Handler)

Option configures optional Handler behavior at construction time.

func WithUpstreamFilter added in v0.34.0

func WithUpstreamFilter(f UpstreamFilter) Option

WithUpstreamFilter injects a filter that narrows the authorization chain to a subset of the configured upstreams once the first leg resolves. When unset, the handler walks all configured upstreams — the behavior before this option existed. See UpstreamFilter for the contract.

func WithUpstreamRefresher added in v0.31.0

func WithUpstreamRefresher(r storage.UpstreamTokenRefresher) Option

WithUpstreamRefresher injects a refresher used by nextMissingUpstream to transparently refresh expired upstream tokens while evaluating the authorization chain. When unset, an expired leg is treated as missing and the user is re-prompted — the behavior before this option existed.

type UpstreamFilter added in v0.34.0

type UpstreamFilter interface {
	FilterUpstreams(
		ctx context.Context,
		principal auth.PrincipalInfo,
		configured []string,
	) ([]string, error)
}

UpstreamFilter narrows the authorization chain to a subset of the configured upstreams, keyed on the identity established by the first upstream. It is consulted exactly once per authorization, in the callback handler, after the first upstream (upstreams[0]) resolves. The first upstream is always required: it is never passed to the filter and cannot be removed by it.

FilterUpstreams receives, in order:

  • ctx: the request context of the first leg's callback.
  • principal: the upstream-derived identity for authorization decisions. Its PlatformUserID is the canonical ToolHive user ID (the stable internal identifier); Subject is the claim-mapped upstream subject (OIDC SubjectClaim, or "sub"); Claims carries the ID-token/userinfo claims; Name and Email are populated when the upstream provides them. It carries NO tokens — it is the credential-free auth.PrincipalInfo projection. The filter MUST treat principal (including its Claims map) as read-only.
  • configured: the names of the non-first configured upstreams, in configured order.

Claims contract: values are untyped and come straight from the upstream IdP, so implementations MUST assert types defensively (e.g. comma-ok on map/slice access) — a malformed IdP response must not be able to panic the filter. Claims is nil not only for OAuth2 identityFromToken / synthetic upstreams (which resolve no structured claim set) but also on a transient OIDC claims-extraction failure, so a security-relevant filter MUST treat nil/absent claims as fail-closed. Note that the `aud` claim, when present, is the upstream IdP's client_id — not this authorization server.

It returns the subset to keep. The handler preserves configured order and ignores any returned name that is not one of the non-first configured upstreams, so the filter cannot reorder the chain or introduce unknown providers. A returned error fails the authorization with a server error — the handler never falls back to walking every upstream on error.

The "first-leg identity" guarantee holds even across a rolling upgrade of a persistent backend: a subsequent-leg pending that lacks a computed chain (e.g. one written before this feature existed) is rejected and forces a fresh authorization, rather than re-running the filter against a later leg.

type UserResolver added in v0.8.1

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

UserResolver handles finding or creating users based on provider identity. It manages the mapping between upstream provider subjects and internal user IDs.

func NewUserResolver added in v0.8.1

func NewUserResolver(stor storage.UserStorage) *UserResolver

NewUserResolver creates a new UserResolver with the given storage.

func (*UserResolver) ResolveUser added in v0.8.1

func (r *UserResolver) ResolveUser(
	ctx context.Context,
	providerID string,
	providerSubject string,
) (*storage.User, error)

ResolveUser finds an existing user or creates a new one for the provider identity. Returns the user whose ID will be the "sub" claim in our JWTs.

The resolution process: 1. Look up existing identity by (providerID, providerSubject) 2. If found, return the linked user 3. If not found, create a new user and link the identity

func (*UserResolver) UpdateLastAuthenticated added in v0.8.1

func (r *UserResolver) UpdateLastAuthenticated(
	ctx context.Context,
	providerID string,
	providerSubject string,
)

UpdateLastAuthenticated updates the last authentication timestamp for a provider identity. This supports OIDC max_age parameter enforcement by tracking when users last authenticated. Errors are logged but not fatal - callers should continue with authorization.

Jump to

Keyboard shortcuts

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