authn

package
v0.18.1 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: MIT Imports: 15 Imported by: 2

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrMissingAuthorization = errors.New("missing authorization")
	ErrInvalidCredentials   = errors.New("invalid credentials")
)
View Source
var ErrInvalidToken = errors.New("invalid token")

ErrInvalidToken is returned when a bearer token is missing required properties, fails signature verification, or fails claim validation.

View Source
var ErrNoKeys = errors.New("authn: no verification keys")

ErrNoKeys is returned by a KeySource that has never successfully obtained any key material (e.g. a JWKS whose first fetch failed). Once any key set has been fetched, a later refresh failure is absorbed (fail-static) rather than surfaced.

Functions

func JWTClaimsFromContext added in v0.15.1

func JWTClaimsFromContext(ctx context.Context) (map[string]any, bool)

JWTClaimsFromContext returns the verified JWT claims that JWTAuthenticator stored on the request context, if the request was authenticated by it.

Example

Read the verified claims that authn.JWT placed on the request context from a downstream handler.

package main

import (
	"fmt"
	"net/http"

	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		claims, ok := authn.JWTClaimsFromContext(r.Context())
		if !ok {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
		fmt.Fprintf(w, "subject: %v", claims["sub"])
	})
	_ = h
}

Types

type Authenticator

type Authenticator struct {
	Type         string
	Authenticate func(*http.Request) error
	Forbidden    func(w http.ResponseWriter, r *http.Request, err error)

	// ShareValueSlice writes the WWW-Authenticate value from a single slice
	// shared across requests instead of allocating one per unauthenticated
	// response. Type is fixed at construction, so this is safe as long as
	// nothing mutates the response header value slice in place. Off by
	// default; see header.SetShared.
	ShareValueSlice bool
}

Authenticator middleware

Example

Authenticator is the building block the other helpers wrap. Use it directly for a custom scheme — here, a static API key. Type is echoed in the WWW-Authenticate header on a 401.

package main

import (
	"net/http"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	m := authn.Authenticator{
		Type: "Bearer",
		Authenticate: func(r *http.Request) error {
			if r.Header.Get("X-API-Key") == "secret-key" {
				return nil
			}
			return authn.ErrMissingAuthorization
		},
	}

	s := parapet.New()
	s.Use(m)
}

func (Authenticator) ServeHandler

func (m Authenticator) ServeHandler(h http.Handler) http.Handler

ServeHandler implements middleware interface

type BasicAuthenticator

type BasicAuthenticator struct {
	Realm        string
	Authenticate func(r *http.Request, username, password string) error

	// ShareValueSlice writes the WWW-Authenticate value from a single slice
	// shared across requests instead of allocating one per unauthenticated
	// response. The value is fixed at construction (it depends only on Realm),
	// so this is safe as long as nothing mutates the response header value
	// slice in place. Off by default; see header.SetShared.
	ShareValueSlice bool
}

BasicAuthenticator middleware

Example

Verify Basic credentials against a custom backend by setting Authenticate directly, with a realm reported in the WWW-Authenticate challenge.

package main

import (
	"net/http"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	m := &authn.BasicAuthenticator{
		Realm: "admin area",
		Authenticate: func(_ *http.Request, username, password string) error {
			// look the user up in a store, verify the password hash, etc.
			if username == "admin" && password == "s3cret" {
				return nil
			}
			return authn.ErrInvalidCredentials
		},
	}

	s := parapet.New()
	s.Use(m)
}

func Basic

func Basic(username, password string) *BasicAuthenticator

Basic creates new basic auth middleware

Example

Require HTTP Basic credentials. Comparison is constant-time.

package main

import (
	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	s := parapet.New()
	s.Use(authn.Basic("admin", "s3cret"))
}

func (BasicAuthenticator) ServeHandler

func (m BasicAuthenticator) ServeHandler(h http.Handler) http.Handler

ServeHandler implements middleware interface

type ForwardAuthenticator added in v0.11.0

type ForwardAuthenticator struct {
	URL                 *url.URL
	Client              *http.Client
	AuthRequestHeaders  []string
	AuthResponseHeaders []string
}

ForwardAuthenticator middleware

func Forward added in v0.11.0

func Forward(url *url.URL) *ForwardAuthenticator

Forward creates new auth request middleware

Example

Delegate the auth decision to an external auth server: the request is allowed when the server returns 2xx, and selected response headers are copied onto the request for downstream handlers.

package main

import (
	"net/url"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	u, _ := url.Parse("http://auth.internal/verify")

	m := authn.Forward(u)
	m.AuthResponseHeaders = []string{"X-Auth-User", "X-Auth-Roles"}

	s := parapet.New()
	s.Use(m)
}

func (ForwardAuthenticator) ServeHandler added in v0.11.0

func (m ForwardAuthenticator) ServeHandler(h http.Handler) http.Handler

type ForwardServerError added in v0.11.0

type ForwardServerError struct {
	Response         *http.Response
	OriginError      error
	StatusCode       int
	IsTransportError bool
}

func (*ForwardServerError) Error added in v0.11.0

func (err *ForwardServerError) Error() string

type JWKS added in v0.18.0

type JWKS struct {
	// URL is the jwks_uri to fetch the key set from. Required.
	URL string

	// Client fetches the key set. Defaults to an *http.Client with a 10s
	// timeout. Give it a tighter timeout or a custom transport as needed.
	Client *http.Client

	// RefreshInterval is how long a fetched set is served before it is treated
	// as stale and refetched (in the background) on the next use. Defaults to
	// 15m.
	RefreshInterval time.Duration

	// MinRefreshInterval rate-limits unknown-kid refetches: a token whose kid is
	// absent from the cached set triggers a refetch only if at least this long
	// has passed since the last fetch. It stops a flood of tokens bearing bogus
	// kids from hammering the jwks_uri. Defaults to 1m.
	MinRefreshInterval time.Duration

	// MaxResponseBytes caps how many bytes of the JWKS response body are read,
	// defending against a hostile or runaway endpoint. Defaults to 1 MiB.
	MaxResponseBytes int64

	// Now overrides the clock used for cache aging; mainly for tests. Defaults
	// to time.Now.
	Now func() time.Time
	// contains filtered or unexported fields
}

JWKS is a KeySource backed by a remote JSON Web Key Set — an OIDC provider's jwks_uri (e.g. Auth0, Okta, Google). It fetches the set over HTTP and caches it, refetching when the cache goes stale or when a token presents a kid the cache doesn't know, so signing-key rotation is picked up without a restart.

Fetches are single-flighted: concurrent verifications that need a refresh share one HTTP request. It is fail-static — once a key set has been fetched, a later refresh failure keeps the last good set in service instead of rejecting requests. A merely-stale refresh happens in the background and serves the cached set meanwhile; only an empty cache or an unknown kid blocks the caller on the fetch.

JWKS is safe for concurrent use. The zero value is not usable: set URL (and optionally the tuning fields) and pass it as JWTAuthenticator.KeySource.

Example

Tune the JWKS cache and accept more than one algorithm.

package main

import (
	"time"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	jwks := &authn.JWKS{
		URL:                "https://issuer.example.com/.well-known/jwks.json",
		RefreshInterval:    15 * time.Minute, // serve a cached set this long before refreshing
		MinRefreshInterval: time.Minute,      // rate-limit unknown-kid refetches
		MaxResponseBytes:   1 << 20,          // cap the response body at 1 MiB
	}

	s := parapet.New()
	s.Use(authn.JWTFromKeySource(jwks, authn.RS256, authn.ES256))
}

func (*JWKS) VerificationKey added in v0.18.0

func (j *JWKS) VerificationKey(ctx context.Context, kid string) (any, error)

VerificationKey implements KeySource. It returns the cached key set, refreshing it as needed (see JWKS).

type JWTAuthenticator added in v0.15.1

type JWTAuthenticator struct {
	// Key verifies the token signature. See JWT for accepted types. Ignored when
	// KeySource is set.
	Key any

	// KeySource, when set, supplies the verification key dynamically at request
	// time (e.g. a rotating remote JWKS via JWKS) and takes precedence over Key.
	// The token's "kid" header is passed to it so it can select or refresh keys.
	KeySource KeySource

	// Algorithms is the set of accepted signature algorithms. It is required;
	// when empty every request is rejected.
	Algorithms []SignatureAlgorithm

	// Issuer and Audience, when set, must match the token's "iss" and "aud"
	// claims respectively.
	Issuer   string
	Audience string

	// Leeway tolerates clock skew when checking the time-based claims "exp",
	// "nbf" and "iat". Defaults to jwt.DefaultLeeway (1 minute).
	Leeway time.Duration

	// Realm is reported in the WWW-Authenticate challenge on rejection.
	Realm string

	// Now overrides the clock used for claim validation. Defaults to time.Now;
	// mainly useful for tests.
	Now func() time.Time

	// ShareValueSlice shares the fixed WWW-Authenticate value slice across
	// rejected responses instead of allocating one per request. See
	// Authenticator.ShareValueSlice.
	ShareValueSlice bool
}

JWTAuthenticator middleware

func JWT added in v0.15.1

func JWT(key any, algs ...SignatureAlgorithm) *JWTAuthenticator

JWT creates a new JWT bearer-token authentication middleware. It reads the token from the Authorization: Bearer header, verifies its signature against key, and accepts only the listed signature algorithms.

The algorithm allowlist is mandatory: a token signed with any other algorithm — including "none" — is rejected. Pinning the algorithms this way prevents algorithm-confusion attacks, where an attacker re-signs a token with an algorithm the verifier did not intend to accept.

algs pins the accepted algorithms with this package's SignatureAlgorithm constants (e.g. authn.RS256) — callers don't import a JOSE library.

key is a standard crypto key: []byte for HMAC (HS256/384/512), or an *rsa.PublicKey, *ecdsa.PublicKey or ed25519.PublicKey for asymmetric signatures.

To verify against a rotating remote key set instead of a static key, leave key nil and set JWTAuthenticator.KeySource (see JWTFromKeySource and JWKS).

Example

Verify HS256 bearer tokens signed with a shared secret, requiring matching iss/aud claims. Algorithms are pinned with this package's constants, so no JOSE library is imported.

package main

import (
	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	m := authn.JWT([]byte("0123456789abcdef0123456789abcdef"), authn.HS256)
	m.Issuer = "https://issuer.example.com"
	m.Audience = "my-api"

	s := parapet.New()
	s.Use(m)
}
Example (PublicKey)

Verify asymmetric tokens against the issuer's public key. key may be an *rsa.PublicKey, *ecdsa.PublicKey or ed25519.PublicKey — typically parsed from PEM with x509.ParsePKIXPublicKey. Pin the matching algorithm(s).

package main

import (
	"crypto/rsa"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	var pub *rsa.PublicKey // e.g. from x509.ParsePKIXPublicKey

	s := parapet.New()
	s.Use(authn.JWT(pub, authn.RS256))
}

func JWTFromKeySource added in v0.18.0

func JWTFromKeySource(src KeySource, algs ...SignatureAlgorithm) *JWTAuthenticator

JWTFromKeySource creates a JWT bearer-token authentication middleware that resolves its verification key from src at request time — typically a remote, rotating JWKS via JWKS — instead of a fixed key. The algorithm allowlist is still mandatory and enforced exactly as in JWT.

Example

Verify tokens against an OIDC provider's rotating JWKS (jwks_uri) instead of a static key, so signing-key rotation is picked up without a restart.

package main

import (
	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

func main() {
	m := authn.JWTFromKeySource(
		&authn.JWKS{URL: "https://issuer.example.com/.well-known/jwks.json"},
		authn.RS256, // pin the accepted algorithm(s)
	)
	m.Issuer = "https://issuer.example.com"
	m.Audience = "my-api"

	s := parapet.New()
	s.Use(m)
}

func (JWTAuthenticator) ServeHandler added in v0.15.1

func (m JWTAuthenticator) ServeHandler(h http.Handler) http.Handler

ServeHandler implements middleware interface

type KeySource added in v0.18.0

type KeySource interface {
	VerificationKey(ctx context.Context, kid string) (any, error)
}

KeySource supplies the verification key for JWTAuthenticator at request time, which lets the signing key rotate without restarting the process. kid is the "kid" from the token's JOSE header ("" when the token carries none); an implementation may use it to decide whether a refresh is warranted.

The returned value must be a key the verifier accepts: a standard public key (*rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey) or an HMAC []byte (see JWT). The built-in JWKS returns a key set keyed by "kid"; the token's pinned algorithm is still enforced by JWTAuthenticator regardless of what is returned.

Example

Plug a custom KeySource into the JWT authenticator.

package main

import (
	"context"
	"crypto/rsa"
	"fmt"

	"github.com/moonrhythm/parapet"
	"github.com/moonrhythm/parapet/pkg/authn"
)

// kidKeys is a custom KeySource backed by a fixed table of public keys keyed by
// the token's "kid". Any KeySource can supply verification keys; the built-in
// JWKS is one implementation.
type kidKeys map[string]any

func (k kidKeys) VerificationKey(_ context.Context, kid string) (any, error) {
	key, ok := k[kid]
	if !ok {
		return nil, fmt.Errorf("unknown kid %q", kid)
	}
	return key, nil
}

func main() {
	var current, previous *rsa.PublicKey // during a rotation, accept both

	keys := kidKeys{"2025-current": current, "2025-previous": previous}

	s := parapet.New()
	s.Use(authn.JWTFromKeySource(keys, authn.RS256))
}

type SignatureAlgorithm added in v0.18.0

type SignatureAlgorithm string

SignatureAlgorithm is a JWS signature (or MAC) algorithm accepted by JWTAuthenticator. The values are the standard JWA names from RFC 7518; pin the exact algorithm(s) your tokens are signed with so a token signed with any other algorithm — including "none" — is rejected.

Use these constants instead of importing a JOSE library directly: the common path needs only this package.

const (
	HS256 SignatureAlgorithm = "HS256" // HMAC using SHA-256
	HS384 SignatureAlgorithm = "HS384" // HMAC using SHA-384
	HS512 SignatureAlgorithm = "HS512" // HMAC using SHA-512
	RS256 SignatureAlgorithm = "RS256" // RSASSA-PKCS#1 v1.5 using SHA-256
	RS384 SignatureAlgorithm = "RS384" // RSASSA-PKCS#1 v1.5 using SHA-384
	RS512 SignatureAlgorithm = "RS512" // RSASSA-PKCS#1 v1.5 using SHA-512
	ES256 SignatureAlgorithm = "ES256" // ECDSA using P-256 and SHA-256
	ES384 SignatureAlgorithm = "ES384" // ECDSA using P-384 and SHA-384
	ES512 SignatureAlgorithm = "ES512" // ECDSA using P-521 and SHA-512
	PS256 SignatureAlgorithm = "PS256" // RSASSA-PSS using SHA-256 and MGF1 with SHA-256
	PS384 SignatureAlgorithm = "PS384" // RSASSA-PSS using SHA-384 and MGF1 with SHA-384
	PS512 SignatureAlgorithm = "PS512" // RSASSA-PSS using SHA-512 and MGF1 with SHA-512
	EdDSA SignatureAlgorithm = "EdDSA" // EdDSA using Ed25519
)

Supported signature algorithms.

Jump to

Keyboard shortcuts

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