authn

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 20 Imported by: 0

README

authn

Transport-agnostic request authentication for Go — API keys, JWT/OIDC, and mTLS, with a pluggable authorization predicate

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base. Docs: authn.go.phpboyscout.uk


gitlab.com/phpboyscout/go/authn — small, transport-agnostic credential verification primitives for Go servers. It carries no HTTP or gRPC imports, so the same verifiers plug into an HTTP middleware, a gRPC interceptor, or a tool calling them directly.

A Verifier authenticates a credential and returns a verified Identity; authorization is a single tool-supplied predicate (AuthorizeFunc) — the package ships no policy engine. It is the auth layer extracted from go-tool-base, where it backs both server transports.

Design

  • Framework-free. The only external dependencies are cockroachdb/errors and golang-jwt/jwt/v5. No HTTP, no gRPC, no config framework, no go-tool-base — a depfootprint_test.go guard enforces it.
  • Fail closed, leak nothing. A Verifier never encodes the reason for failure in a user-facing form; callers map any verify error to a generic 401 / Unauthenticated and log the wrapped detail server-side (redacted). The ErrUnauthenticated sentinel never crosses the wire.
  • Separation of authn and authz. Verifiers answer who are you; the AuthorizeFunc predicate answers what may you do. Compose the built-ins (RequireScopes, RequireClaim) or supply your own.

Install

go get gitlab.com/phpboyscout/go/authn

Quick start

package main

import (
	"context"

	"gitlab.com/phpboyscout/go/authn"
)

func main() {
	// API-key verification from a set of configured keys.
	verifier, err := authn.NewAPIKeyVerifier(
		authn.KeyEntry{Key: "s3cr3t", Subject: "ci-bot"},
	)
	if err != nil {
		panic(err)
	}

	id, err := verifier.Verify(context.Background(), "s3cr3t")
	if err != nil {
		// map to 401 / Unauthenticated; log err server-side (redacted)
		return
	}
	_ = id.Subject // "ci-bot"
}

What's inside

  • VerifiersNewAPIKeyVerifier (constant-time key lookup), NewJWTVerifier (HS/RS/ES signing, exp/nbf/iss/aud validation, JWKS with caching and WithOIDCDiscovery), and NewMTLSVerifier (client-certificate identity, with a configurable subject extractor via WithCertSubject). API-key and JWT satisfy the Verifier interface; mTLS uses CertVerifier because a certificate is a transport property, not a credential string.
  • Identity — the verified outcome: Subject, Method (apikey/jwt/mtls), verified Claims, and parsed Scopes.
  • AuthorizationAuthorizeFunc plus the RequireScopes and RequireClaim combinators; RequestMetadata gives predicates access to request context.
  • Context plumbingContextWithIdentity / IdentityFromContext and the RequestMetadata equivalents, for passing the verified principal down the stack.

Documentation

Full guides and the security model: authn.go.phpboyscout.uk. API reference: pkg.go.dev.

License

See LICENSE.

Documentation

Overview

Package authn provides transport-agnostic credential verification primitives for the HTTP and gRPC server transports. It carries no HTTP or gRPC imports, so it is trivially unit-testable and reusable from both transports (and by tools directly).

A Verifier authenticates a credential and returns the verified Identity, or an error. Verifiers MUST NOT leak the reason for failure in a user-facing form — the returned error is for the server log only; the transport adapters map it to a generic wire status (401 / Unauthenticated) and log the detail redacted. Authorization (deciding what a verified principal may do) is a single tool-supplied predicate, AuthorizeFunc — GTB ships no policy engine.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnauthenticated = errors.New("authn: unauthenticated")

ErrUnauthenticated is the sentinel a Verifier wraps when a credential fails verification. Callers test for it with errors.Is, but treat any non-nil verify error the same way: a generic 401 / Unauthenticated on the wire, the wrapped detail logged server-side (redacted). It never crosses the wire.

Functions

func ContextWithIdentity

func ContextWithIdentity(ctx context.Context, id *Identity) context.Context

ContextWithIdentity returns a child context carrying the verified Identity. Auth adapters call this on success so downstream handlers can read it.

func ContextWithRequestMetadata

func ContextWithRequestMetadata(ctx context.Context, m RequestMetadata) context.Context

ContextWithRequestMetadata returns a child context carrying request metadata for AuthorizeFunc. Auth adapters call this before running authorization.

Types

type AuthorizeFunc

type AuthorizeFunc func(ctx context.Context, id *Identity) bool

AuthorizeFunc decides whether an authenticated Identity may proceed. It runs AFTER successful verification; returning false yields 403 / PermissionDenied.

This is the ENTIRE authorization surface GTB ships — there is no built-in RBAC engine and no policy DSL. It is deliberately shaped as a policy-model hole: it receives the resolved Identity and a context that carries the request metadata the adapter injected (see RequestMetadataFromContext), so a tool can implement arbitrary external authorization — evaluate a role map, call out to OPA, whatever — without GTB shipping the engine. Keep the predicate fast and pure; it runs on every authenticated request.

func RequireClaim

func RequireClaim(name string, value any) AuthorizeFunc

RequireClaim returns an AuthorizeFunc that admits an Identity only if its verified claim named name deep-equals value. (DeepEqual avoids a panic on a non-comparable claim value.)

func RequireScopes

func RequireScopes(scopes ...string) AuthorizeFunc

RequireScopes returns an AuthorizeFunc that admits an Identity only if it carries all of the named scopes. With no scopes it admits any authenticated identity.

type CertVerifier

type CertVerifier interface {
	VerifyCert(ctx context.Context, verifiedChains [][]*x509.Certificate) (*Identity, error)
}

CertVerifier authenticates a verified client-certificate chain from a completed mTLS handshake and returns the Identity, or an error. It is separate from Verifier because a client certificate is a transport property, not a bearer credential string.

The cryptographic verification (chain validation against the configured client CA pool) is the TLS stack's job — the server must be configured for RequireAndVerifyClientCert with a ClientCAs pool. A CertVerifier receives the already-verified chains (as in tls.ConnectionState.VerifiedChains) and only derives identity from them.

func NewMTLSVerifier

func NewMTLSVerifier(opts ...MTLSOption) CertVerifier

NewMTLSVerifier returns a CertVerifier that derives the Subject from the leaf client certificate.

type Identity

type Identity struct {
	// Subject is the principal identifier: the "sub" claim for JWT, the
	// configured label for an API key, or the certificate subject for mTLS.
	Subject string
	// Method records which verifier authenticated the request: "apikey", "jwt",
	// or "mtls".
	Method string
	// Claims holds verified JWT claims (nil for API-key and mTLS auth).
	Claims map[string]any
	// Scopes is the parsed "scope"/"scp" claim when present, for convenience.
	Scopes []string
}

Identity is the verified outcome of a successful authentication. It carries the minimum a downstream handler needs to make authorization decisions.

func IdentityFromContext

func IdentityFromContext(ctx context.Context) (*Identity, bool)

IdentityFromContext returns the verified Identity stored by an auth adapter, and whether one was present. The HTTP and gRPC adapters share this key, so a handler reads identity the same way regardless of transport.

type JWTConfig

type JWTConfig struct {
	// Issuer is the required "iss". Verification fails if it does not match.
	Issuer string
	// Audiences are the acceptable "aud" values (any-of). Empty disables the aud
	// check (logged-worthy; not recommended).
	Audiences []string
	// JWKSURL is the JSON Web Key Set endpoint (HTTPS). Signing keys are fetched
	// and cached from here. Resolved automatically by WithOIDCDiscovery.
	JWKSURL string
	// Leeway tolerates small clock skew on exp/nbf/iat. Default 60s.
	Leeway time.Duration
	// RefreshInterval bounds how often the JWKS is refetched. Default 15m.
	RefreshInterval time.Duration
	// AllowedAlgorithms restricts accepted "alg" values. Default: RS/ES 256/384/512.
	AllowedAlgorithms []string
	// HTTPClient fetches the JWKS (and the OIDC document). Defaults to a client
	// with a sane timeout. Must reach an HTTPS endpoint.
	HTTPClient *http.Client
}

JWTConfig configures the JWT/OIDC bearer-token verifier.

type JWTOption

type JWTOption func(*jwtSettings)

JWTOption configures NewJWTVerifier beyond the base JWTConfig.

func WithOIDCDiscovery

func WithOIDCDiscovery(issuerURL string) JWTOption

WithOIDCDiscovery resolves JWKSURL (and validates Issuer) from the provider's /.well-known/openid-configuration document at construction time. This is the ONLY OIDC affordance: discovery of the JWKS endpoint — no login flow, no token endpoint use. The discovery URL must be HTTPS.

type KeyEntry

type KeyEntry struct {
	Key     string // the shared secret
	Subject string // identity label, e.g. "ci-runner", "admin"
}

KeyEntry pairs a valid API key with the Subject label recorded on success.

type MTLSOption

type MTLSOption func(*mtlsVerifier)

MTLSOption configures the mTLS verifier.

func WithCertSubject

func WithCertSubject(fn func(*x509.Certificate) string) MTLSOption

WithCertSubject overrides how the Subject is derived from the leaf certificate. The default uses the Common Name, then the first DNS SAN, then the first URI SAN.

type RequestMetadata

type RequestMetadata struct {
	// Method is the HTTP method (GET, POST, …) or "grpc" for an RPC.
	Method string
	// Path is the HTTP request path or the gRPC full method name.
	Path string
}

RequestMetadata carries the minimal transport facts an AuthorizeFunc may want for a policy decision — the route being accessed. The auth adapters populate it before running authorization, so a policy can be route-aware without GTB shipping a routing model.

func RequestMetadataFromContext

func RequestMetadataFromContext(ctx context.Context) (RequestMetadata, bool)

RequestMetadataFromContext returns the request metadata an adapter injected, and whether any was present. Use it from an AuthorizeFunc to make a route-aware authorization decision.

type Verifier

type Verifier interface {
	Verify(ctx context.Context, credential string) (*Identity, error)
}

Verifier authenticates a single credential string (the raw bearer token or API key, already extracted from the transport) and returns the verified Identity, or an error. Implementations MUST NOT encode the reason for failure in a user-facing way — callers redact the error and map it to a generic wire status.

mTLS client-certificate authentication does not implement this interface: a certificate is a transport property, not a credential string. See CertVerifier.

func NewAPIKeyVerifier

func NewAPIKeyVerifier(entries ...KeyEntry) (Verifier, error)

NewAPIKeyVerifier returns a Verifier that accepts any of the given keys.

Comparison is constant-time. Because comparing variable-length input against a secret with subtle.ConstantTimeCompare is itself length-leaking (it short-circuits on unequal lengths), the verifier compares a fixed-width SHA-256 digest of the presented credential against the pre-computed digest of each configured key, iterating ALL entries with no early return — so neither match/no-match, key length, nor which key matched is timing-distinguishable.

An empty key set is a construction error (fail-closed: never accept "any key"), as is an entry with an empty key.

func NewJWTVerifier

func NewJWTVerifier(ctx context.Context, cfg JWTConfig, opts ...JWTOption) (Verifier, error)

NewJWTVerifier returns a Verifier for JWT bearer tokens, fetching signing keys from the configured (or OIDC-discovered) JWKS endpoint. It performs an initial JWKS fetch so a misconfiguration fails fast.

Jump to

Keyboard shortcuts

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