auth

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 47 Imported by: 0

Documentation

Overview

Package auth authenticates requests (SPEC §8): browser sessions via the built-in OIDC login (Okta, Entra ID, Google…), identity-aware proxies (Google IAP, AWS ALB OIDC, trusted headers), Backyard API tokens, static tokens and IdP-issued bearer JWTs (device flow). In mode "none" everyone is an anonymous guest identified by a cookie.

Index

Constants

View Source
const (
	ModeNone   = "none"
	ModeOIDC   = "oidc"
	ModeIAP    = "iap"
	ModeALB    = "alb"
	ModeHeader = "header"
)

Normalized auth modes.

View Source
const (
	PathPrefix   = "/__backyard/auth/"
	LoginPath    = PathPrefix + "login"
	CallbackPath = PathPrefix + "callback"
	LogoutPath   = PathPrefix + "logout"
	CLIPath      = PathPrefix + "cli"
)

Paths served by Handler.

View Source
const (
	SessionCookie = "backyard_session"
	// OAuthCookie holds the login state when server.public_url is http.
	OAuthCookie = "backyard_oauth"
	// OAuthHostCookie holds the login state when server.public_url is https:
	// browsers accept a "__Host-" cookie only from the host itself (Secure,
	// Path=/, no Domain), so a site's page cannot plant one on the hub.
	OAuthHostCookie = "__Host-" + OAuthCookie
	AnonCookie      = "backyard_anon"
)

Cookie names.

View Source
const (
	// IAPHeader carries the signed IAP assertion.
	IAPHeader = "X-Goog-IAP-JWT-Assertion"
	// IAPIssuer is the "iss" of IAP assertions.
	IAPIssuer = "https://cloud.google.com/iap"
	// DefaultIAPJWKSURL publishes IAP's ES256 signing keys.
	DefaultIAPJWKSURL = "https://www.gstatic.com/iap/verify/public_key-jwk"
)
View Source
const (
	MCPPath              = "/__backyard/mcp"
	OAuthPathPrefix      = "/__backyard/oauth/"
	OAuthRegisterPath    = OAuthPathPrefix + "register"
	OAuthTokenPath       = OAuthPathPrefix + "token"
	OAuthRevokePath      = OAuthPathPrefix + "revoke"
	ASMetadataPath       = "/.well-known/oauth-authorization-server"
	ResourceMetadataPath = "/.well-known/oauth-protected-resource" + MCPPath
)

Paths of the connector (SPEC §8.4, §14.1). Only AuthorizePath stays behind an identity-aware proxy; the others authenticate by themselves.

View Source
const (
	// TokenIssuer is the "iss" claim of Backyard API tokens.
	TokenIssuer = "backyard"
	// TokenScope is the "scope" claim of Backyard API tokens.
	TokenScope = "api"
	// DefaultTokenTTL is used when NewTokens is given a non-positive TTL.
	DefaultTokenTTL = 720 * time.Hour
)
View Source
const (
	// ALBHeader carries the ALB-signed user claims.
	ALBHeader = "X-Amzn-Oidc-Data"
)
View Source
const AuthorizePath = "/__backyard/auth/authorize"

AuthorizePath is served by Authenticator.Handler() (GET, POST).

Variables

View Source
var (
	// ErrUnauthenticated: no (valid) credentials were presented.
	ErrUnauthenticated = errors.New("auth: unauthenticated")
	// ErrForbidden: authenticated but not allowed (domain/group gate).
	ErrForbidden = errors.New("auth: forbidden")
	// ErrCookieConflict: the browser sent valid session (or guest) cookies
	// of different users, e.g. one planted by a site's page. It is always
	// wrapped together with ErrUnauthenticated.
	ErrCookieConflict = errors.New("auth: the browser sent sign-in cookies of different users")
)
View Source
var ErrInvalidToken = errors.New("auth: invalid MCP access token")

ErrInvalidToken marks an MCP access token that does not authenticate: answer 401 with MCP.Challenge(err).

Functions

func MediaType added in v0.2.0

func MediaType(contentType string) string

MediaType returns the media type of a Content-Type value, lowercased, with its parameters ignored, malformed ones included: the connector's endpoints compare only this, as the Worker does (SPEC §8.4, §14.1).

func WithClientIP added in v0.2.0

func WithClientIP(ctx context.Context, ip string) context.Context

WithClientIP records the client address of the request behind ctx for the audit records of Revoke and RevokeUser (the grants API passes the address the server resolved).

Types

type Authenticator

type Authenticator interface {
	Authenticate(w http.ResponseWriter, r *http.Request) (*identity.User, error)
	// LoginURL returns the URL a browser should be redirected to in order to
	// log in and come back to returnTo, or "" when interactive login is not
	// handled by Backyard (iap/alb/header/none).
	LoginURL(returnTo string) string
	// Handler serves /__backyard/auth/* on the hub host (login, callback,
	// logout, cli). It expects the full request path.
	Handler() http.Handler
	// Mode returns the normalized auth mode.
	Mode() string
}

Authenticator resolves the user behind a request.

Authenticate returns (user, nil) on success, (nil, ErrUnauthenticated) when the request carries no usable credentials, (nil, ErrForbidden) when the user is authenticated but rejected by allowed_domains/allowed_emails/allowed_groups, or another error for infrastructure failures. Bearer tokens (API, static, IdP JWT) are accepted in every mode. In mode "none" Authenticate only fails when an invalid bearer token is presented, or when the browser sends valid guest cookies of different guests (ErrUnauthenticated, with ErrCookieConflict); otherwise it returns an anonymous user and, when w is non-nil, sets the backyard_anon cookie. On a cookie conflict Authenticate expires the cookies Backyard did not set itself when w is non-nil.

func New

func New(ctx context.Context, cfg *config.Config, opts Options) (Authenticator, error)

New builds the authenticator for cfg.Auth.Mode. For mode "oidc" it performs OIDC discovery against the issuer (ctx bounds that call).

HTTPClient defaults to a client with a 15 s timeout (a client without a timeout gets one). When auth.oidc.issuer is configured in a mode other than oidc (device flow behind a proxy), discovery for IdP bearer tokens is attempted here and, if the IdP is unreachable, retried lazily.

func NewBearer

func NewBearer(ctx context.Context, cfg *config.Config, opts Options) (Authenticator, error)

NewBearer builds a bearer-only authenticator for the standalone deploy gateway: it accepts Backyard API tokens, static tokens and (if cfg.Auth.OIDC.Issuer is set) IdP-issued JWTs — never cookies or proxy headers. Handler() serves nothing and LoginURL returns "".

IdP discovery is attempted here but never fails construction: when the IdP is unreachable it is retried lazily when the first IdP token arrives.

type Grant added in v0.2.0

type Grant struct {
	ID, ClientID, ClientName, ClientKind, ClientHost, RedirectURI, Scope, Resource string
	// User is the snapshot taken at consent: ID, Email, Name, Groups, Anonymous.
	User                 identity.User
	CreatedAt, ExpiresAt time.Time
}

Grant is one connection: the consent of a user to one MCP client.

type MCP added in v0.2.0

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

MCP is the connector's authorization server. It is nil when mcp.enabled is false.

func MCPOf added in v0.2.0

func MCPOf(a Authenticator) *MCP

MCPOf returns the authorization server of a (nil when MCP is disabled, a is bearer-only, or a is not built by New).

func (*MCP) AllowedOrigin added in v0.2.0

func (m *MCP) AllowedOrigin(origin string) bool

AllowedOrigin reports whether a present Origin header is accepted at /__backyard/mcp besides the hub origin, which the caller checks (SPEC §14.1, gate step 3): the origins of the https entries of mcp.redirect_uris and of the entries of mcp.clients (P is accepted too).

func (*MCP) Challenge added in v0.2.0

func (m *MCP) Challenge(err error) string

Challenge returns the exact WWW-Authenticate value for err (SPEC §14.1); a nil err gives the missing-token form.

func (*MCP) Close added in v0.2.0

func (m *MCP) Close(ctx context.Context) error

Close waits for the background work of the authorization server (grant pruning) to finish, or for ctx to end. Call it after the HTTP server has stopped and before the document store is closed.

func (*MCP) GrantByID added in v0.2.0

func (m *MCP) GrantByID(ctx context.Context, id string) (*Grant, error)

GrantByID returns a grant, or nil, nil when it does not exist.

func (*MCP) Grants added in v0.2.0

func (m *MCP) Grants(ctx context.Context, userID string) ([]Grant, error)

Grants lists the live grants of userID, newest first, at most 200.

func (*MCP) Issuer added in v0.2.0

func (m *MCP) Issuer() string

Issuer returns P, the canonical hub origin.

func (*MCP) MaxBodyBytes added in v0.2.0

func (m *MCP) MaxBodyBytes() int64

MaxBodyBytes is the MCP request body limit: 2 × mcp.max_publish_bytes + 1 MiB.

func (*MCP) Resource added in v0.2.0

func (m *MCP) Resource() string

Resource returns R, P + "/__backyard/mcp".

func (*MCP) ResourceMetadataURL added in v0.2.0

func (m *MCP) ResourceMetadataURL() string

ResourceMetadataURL returns M, the protected resource metadata URL.

func (*MCP) Revoke added in v0.2.0

func (m *MCP) Revoke(ctx context.Context, id string, actor *identity.User, via string) (bool, error)

Revoke deletes one grant; via names the lever (rfc7009, hub, policy, replay).

func (*MCP) RevokeUser added in v0.2.0

func (m *MCP) RevokeUser(ctx context.Context, userID string, actor *identity.User, via string) (int, error)

RevokeUser deletes every grant of userID and returns how many there were.

func (*MCP) ServeMetadata added in v0.2.0

func (m *MCP) ServeMetadata(w http.ResponseWriter, r *http.Request)

ServeMetadata serves both discovery documents (SPEC §8.4.2).

func (*MCP) ServeOAuth added in v0.2.0

func (m *MCP) ServeOAuth(w http.ResponseWriter, r *http.Request)

ServeOAuth serves /__backyard/oauth/{register,token,revoke}.

func (*MCP) VerifyAccessToken added in v0.2.0

func (m *MCP) VerifyAccessToken(ctx context.Context, raw string) (*identity.User, *Grant, error)

VerifyAccessToken returns the re-gated user and the grant of an MCP access token. errors.Is(err, ErrInvalidToken) means 401 invalid_token (Challenge gives the header); any other error is an infrastructure failure (503).

type Options

type Options struct {
	// Directory enriches users after claim mapping (optional).
	Directory identity.Directory
	// HTTPClient is used for OIDC discovery and JWKS/key fetches (default: a
	// client with a 15 s timeout; a client without a timeout gets one).
	HTTPClient *http.Client
	// Logger (default slog.Default()).
	Logger *slog.Logger
	// Now overrides the clock (tests).
	Now func() time.Time
	// Docs stores the grants of the MCP connector's authorization server;
	// required by New when mcp.enabled is true (nil for NewBearer).
	Docs docstore.Store
	// MCPHTTPClient replaces the SSRF-guarded client that fetches client
	// metadata documents (tests only). Redirects are never followed and the
	// 5 s timeout applies either way.
	MCPHTTPClient *http.Client
	// MCPAllow charges one request of the general API limit
	// (ratelimit.api_per_minute) for key at the connector's authorize and
	// token endpoints and reports how long to wait when it is spent (nil:
	// no limit). The server passes its AllowAPI.
	MCPAllow func(key string) (bool, time.Duration)
}

Options are optional dependencies for authenticators.

type TokenClaims

type TokenClaims struct {
	Subject string   `json:"sub"`
	Email   string   `json:"email,omitempty"`
	Name    string   `json:"name,omitempty"`
	Groups  []string `json:"groups,omitempty"`
	Admin   bool     `json:"admin,omitempty"`
	Scope   string   `json:"scope"`
}

TokenClaims are the claims of a Backyard API token (SPEC §8.3).

type Tokens

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

Tokens mints and verifies Backyard API tokens (SPEC §8.3).

Tokens are HS256 JWTs keyed with keys.Derive(server.secret, keys.APIToken) carrying {iss:"backyard", sub, email, name, groups, admin, scope:"api", jti, iat, exp}. Rotating server.secret revokes every token. A Tokens value is immutable and safe for concurrent use.

func NewTokens

func NewTokens(secret string, defaultTTL time.Duration) *Tokens

NewTokens returns a token issuer/verifier keyed from secret.

func (*Tokens) Issue

func (t *Tokens) Issue(u *identity.User, ttl time.Duration) (token string, expiresAt time.Time, err error)

Issue mints a token for u with scope "api". ttl <= 0 uses the default TTL.

The token always fits the size Verify accepts: an overlong name is shortened and, if needed, trailing groups are dropped (callers that care which groups survive put the important ones first).

func (*Tokens) Verify

func (t *Tokens) Verify(token string) (*identity.User, error)

Verify validates a token and returns the user it represents.

Only HS256 is accepted (no "none", no asymmetric algorithms); iss must be "backyard", scope "api", and exp/iat are checked with 30 seconds of leeway. Errors wrap ErrUnauthenticated. The returned user carries Admin as recorded in the token; subjects starting with "anon:" are anonymous.

Jump to

Keyboard shortcuts

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