Documentation
¶
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrMissingAuthorization = errors.New("missing authorization") ErrInvalidCredentials = errors.New("invalid credentials") )
var ErrInvalidToken = errors.New("invalid token")
ErrInvalidToken is returned when a bearer token is missing required properties, fails signature verification, or fails claim validation.
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
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
}
Output:
Types ¶
type Authenticator ¶
type Authenticator struct {
Type string
Authenticate func(*http.Request) error
Forbidden func(w http.ResponseWriter, r *http.Request, err error)
// 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)
}
Output:
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
// 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)
}
Output:
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"))
}
Output:
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)
}
Output:
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))
}
Output:
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
// 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)
}
Output:
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))
}
Output:
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)
}
Output:
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
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))
}
Output:
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.