prm

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

go-protected-resource-metadata

Go Reference

A typed implementation of RFC 9728 — OAuth 2.0 Protected Resource Metadata (Proposed Standard, 2025-04). Spec: https://www.rfc-editor.org/rfc/rfc9728.html

An OAuth 2.0 protected resource publishes a small JSON document — served at /.well-known/oauth-protected-resource — that advertises which authorization servers it trusts, which scopes it exposes, and how it expects tokens, so a client (increasingly an AI agent or MCP client) can discover how to obtain a usable token without out-of-band configuration. A 401 can point clients straight at that document through the resource_metadata parameter of a WWW-Authenticate challenge.

This library provides the typed metadata document, both halves of the well-known endpoint — a server side that publishes it and a client side that fetches and validates it — and a helper for the resource_metadata challenge parameter.

Standard library only. No JOSE, no JWT signature verification, no framework glue.

Install

go get github.com/hstern/go-protected-resource-metadata

Requires Go 1.26+. The package is imported as prm.

Server: publish the metadata document

Build a Metadata, then mount its Handler at the path WellKnownRequestPath returns:

m := &prm.Metadata{
	Resource:               "https://resource.example.com",
	AuthorizationServers:   []string{"https://as.example.com"},
	ScopesSupported:        []string{"profile", "email"},
	BearerMethodsSupported: []string{prm.BearerMethodHeader},
}

path, err := prm.WellKnownRequestPath(m.Resource) // /.well-known/oauth-protected-resource
if err != nil {
	return err
}
mux := http.NewServeMux()
mux.Handle(path, m.Handler(prm.WithMaxAge(time.Hour)))

Handler validates the document once, serves it as application/json to GET and HEAD (rejecting other methods with 405), and supports HTTP caching via WithMaxAge and WithETag (an If-None-Match match returns 304). For an endpoint you already run, ServeMetadata(w, m) is the à-la-carte primitive — no router imposed.

The well-known segment is inserted between the host and any path of the resource identifier (RFC 9728 §3.1), so a resource named https://resource.example.com/tenant1 serves at /.well-known/oauth-protected-resource/tenant1. WellKnownRequestPath computes that for you.

Client: fetch and validate

m, err := prm.Fetch(ctx, http.DefaultClient, "https://resource.example.com")
if err != nil {
	// transport failure, a non-200 status, an undecodable body, or a
	// resource mismatch — see the error model below
	return err
}
// m.AuthorizationServers, m.ScopesSupported, m.BearerMethodsSupported, ...

Fetch builds the well-known URL, retrieves the document over the *http.Client you pass (TLS and timeouts are its concern), and enforces the RFC 9728 §3.3/§3.4 anti-mix-up check: the document's resource must be identical to the identifier you requested, or Fetch returns ErrResourceMismatch and discards the document.

switch {
case errors.Is(err, prm.ErrResourceMismatch):
	// the returned resource did not match the requested identifier (§3.4)
case errors.Is(err, prm.ErrUnexpectedStatus):
	var he *prm.HTTPError
	errors.As(err, &he) // he.StatusCode has the exact code
case errors.Is(err, prm.ErrInvalidResponse):
	// a 200 body that would not decode
}

When the metadata URL comes from a WWW-Authenticate challenge rather than from constructing the well-known path, use FetchMetadataURL, passing the URL you used to call the resource server as the expected resource (§3.3).

The resource_metadata challenge (§5.1)

ChallengeParam returns the bare (name, value) pair to add to a WWW-Authenticate challenge you are already building — for example the "extra" parameters of a bearer-token library — without coupling to it:

name, value := prm.ChallengeParam(metadataURL)
// name == "resource_metadata"; the serializer quotes value:
//   WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource"

Extension and internationalized members

Members beyond the RFC 9728 names — service-specific extensions and the §2.1 name#lang internationalized variants — round-trip byte-for-byte through Extra and are read or written with typed accessors:

var feature string
present, err := m.GetExtra("x_example_org_feature", &feature)

name, ok := m.Localized("resource_name", "fr") // tagged variant, untagged fallback

Signed metadata (§2.2)

signed_metadata is a JWT (JWS Compact Serialization) that restates the metadata as signed claims. This library parses and exposes it but does not verify the signature — that is a JOSE concern for the caller:

sm, err := m.ParseSignedMetadata() // (nil, nil) if absent; no signature check
// verify sm.SigningInput / sm.Signature with sm.Header against a key for sm.Issuer,
// then apply the §2.2 precedence:
merged, err := sm.Apply(m)

Scope

In scope: the typed Metadata document and Validate, the /.well-known/oauth-protected-resource URL construction (client and server sides), the client Fetch with the §3.3/§3.4 resource-match, the server publish side, the resource_metadata challenge helper, and signed_metadata parse/expose with the §2.2 precedence merge.

Out of scope, by design:

  • JWS verification of signed_metadata — parsed and exposed; verifying the signature is a JOSE concern.
  • Authorization Server Metadata (RFC 8414) — this is the resource side.
  • Token validation and the authorization decision — surfacing the advertised issuers, scopes, and bearer methods is in scope; deciding whether a token is acceptable is the caller's policy.
  • Client-side response caching — the server Handler sets cache headers; the client Fetch keeps no cache of its own.

Versioning

Semantic Versioning, tracked independently of the spec. The current series is v0.x: the public API may still change before v1.0.0. The targeted spec version is exposed as prm.SpecVersion.

License

Apache-2.0 — see LICENSE.

Documentation

Overview

Package prm implements RFC 9728, OAuth 2.0 Protected Resource Metadata: the typed metadata document an OAuth 2.0 protected resource publishes about itself, its /.well-known/oauth-protected-resource endpoint, and the resource_metadata WWW-Authenticate challenge parameter that points clients at it.

A protected resource advertises which authorization servers it trusts, what scopes it exposes, and how it expects tokens, so a client can discover how to obtain a usable token without out-of-band configuration. This package provides the typed document with JSON round-tripping, both halves of the well-known endpoint — a server side that publishes the document and a client side that fetches and validates it — and a helper for the resource_metadata challenge parameter.

The library depends only on the standard library. Verifying the JWS signature of a signed_metadata document is out of scope: the package parses and exposes it, leaving signature verification to the caller and a JOSE library.

Spec: https://www.rfc-editor.org/rfc/rfc9728.html

Index

Examples

Constants

View Source
const (
	BearerMethodHeader = "header"
	BearerMethodBody   = "body"
	BearerMethodQuery  = "query"
)

Bearer token transmission methods for BearerMethodsSupported (RFC 9728 §2, RFC 6750 §2): in the Authorization header, the form-encoded body, or the URI query.

View Source
const ChallengeParamName = "resource_metadata"

ChallengeParamName is the WWW-Authenticate auth-param a protected resource uses to point a client at its metadata document (RFC 9728 §5.1).

View Source
const SpecVersion = "RFC 9728"

SpecVersion is the version of the specification this package targets.

View Source
const WellKnownPathSegment = "/.well-known/oauth-protected-resource"

WellKnownPathSegment is the well-known URI path under which a protected resource serves its metadata document (RFC 9728 §3.1). For a resource identifier with a path component, the resource's own path is appended after this segment; see WellKnownPath for the full construction rule.

Variables

View Source
var (
	// ErrResourceMismatch reports that the fetched document's resource value is
	// not identical to the requested resource identifier (RFC 9728 §3.3/§3.4).
	// The document MUST NOT be used; Fetch returns this and discards it.
	ErrResourceMismatch = errors.New("prm: fetched resource does not match requested resource identifier")
	// ErrUnexpectedStatus reports a non-200 HTTP status. The concrete error is an
	// *HTTPError carrying the status code.
	ErrUnexpectedStatus = errors.New("prm: unexpected HTTP status")
	// ErrInvalidResponse reports a 200 response whose body did not decode as a
	// metadata document.
	ErrInvalidResponse = errors.New("prm: invalid metadata response")
)

Sentinel errors returned by Fetch and FetchMetadataURL. Match them with errors.Is; use errors.As with *HTTPError to read the status code.

View Source
var ErrInvalidSignedMetadata = errors.New("prm: invalid signed_metadata")

ErrInvalidSignedMetadata reports a signed_metadata value that is not a well-formed JWS Compact Serialization, is missing the required iss claim, or nests a signed_metadata claim of its own (RFC 9728 §2.2).

View Source
var ErrValidation = errors.New("prm: validation failed")

ErrValidation is the sentinel that every *ValidationError unwraps to, so a caller can match any validation failure with errors.Is(err, ErrValidation) or inspect the specifics with errors.As.

Functions

func ChallengeParam

func ChallengeParam(metadataURL string) (name, value string)

ChallengeParam returns the WWW-Authenticate challenge parameter that points a client at the protected resource metadata document at metadataURL (RFC 9728 §5.1). It yields the bare (name, value) pair — ("resource_metadata", the URL) — for the caller to add to a challenge it is already building, for example a bearer-token library's challenge "extra" parameters:

name, value := prm.ChallengeParam(metadataURL)
challenge.Extra[name] = value // serialized as resource_metadata="<url>"

The value is the raw URL. The challenge serializer is responsible for wrapping it in the double-quoted auth-param quoted-string the header grammar requires (RFC 9110 §11.2); when writing the header by hand, quote it:

WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource"

Keeping the seam to a plain string pair avoids a dependency on, and a duplicate of, any particular WWW-Authenticate challenge type. §5.1 permits the parameter with non-Bearer schemes (e.g. DPoP) and alongside other parameters.

Example
package main

import (
	"fmt"

	prm "github.com/hstern/go-protected-resource-metadata"
)

func main() {
	name, value := prm.ChallengeParam("https://resource.example.com/.well-known/oauth-protected-resource")
	// A challenge serializer wraps the value in the quoted-string the header grammar wants.
	fmt.Printf("%s=%q\n", name, value)
}
Output:
resource_metadata="https://resource.example.com/.well-known/oauth-protected-resource"

func ServeMetadata

func ServeMetadata(w http.ResponseWriter, m *Metadata) error

ServeMetadata writes m to w as a protected resource metadata response: it validates m (never serve an invalid document), sets Content-Type to application/json, and writes the JSON with status 200. It writes nothing and returns the error if m is invalid (a *ValidationError) or cannot be marshaled.

ServeMetadata is the à-la-carte primitive — it imposes no method handling or routing. Use it inside a handler you already run, or use Handler for a ready http.Handler. Mount either at the path from WellKnownRequestPath.

func WellKnownPath

func WellKnownPath(resource string) (string, error)

WellKnownPath returns the URL of the protected resource metadata document for a resource identifier, following the construction rule of RFC 9728 §3.1.

The well-known segment is inserted between the host and any path component of the resource identifier — it is not appended to the end:

https://resource.example.com           -> https://resource.example.com/.well-known/oauth-protected-resource
https://resource.example.com/resource1 -> https://resource.example.com/.well-known/oauth-protected-resource/resource1

A single terminating slash after the host is removed before insertion, so a bare host and a host with a trailing slash yield the same URL. A query component, if present, is preserved after the inserted path.

The resource identifier must be an https URL with no fragment (§2); otherwise WellKnownPath returns a *ValidationError.

Example
package main

import (
	"fmt"

	prm "github.com/hstern/go-protected-resource-metadata"
)

func main() {
	// The well-known segment is inserted before the resource's own path (§3.1).
	u, _ := prm.WellKnownPath("https://resource.example.com/resource1")
	fmt.Println(u)
}
Output:
https://resource.example.com/.well-known/oauth-protected-resource/resource1

func WellKnownRequestPath

func WellKnownRequestPath(resource string) (string, error)

WellKnownRequestPath returns the request path component at which a protected resource serves its metadata document — the server-side counterpart to WellKnownPath, suitable for registering a handler on an http.ServeMux. It is the path of the URL WellKnownPath produces (§3.1):

https://resource.example.com           -> /.well-known/oauth-protected-resource
https://resource.example.com/resource1 -> /.well-known/oauth-protected-resource/resource1

Any query component of the resource identifier is not part of the mount path and is omitted. The resource identifier must be an https URL with no fragment (§2); otherwise WellKnownRequestPath returns a *ValidationError.

Types

type HTTPError

type HTTPError struct {
	StatusCode int
}

HTTPError is returned when the metadata endpoint responds with a non-200 status. It unwraps to ErrUnexpectedStatus.

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

type HandlerOption

type HandlerOption func(*handlerConfig)

HandlerOption configures the http.Handler returned by Metadata.Handler.

func WithETag

func WithETag(tag string) HandlerOption

WithETag sets the ETag header on the served document and enables 304 Not Modified responses to a matching If-None-Match request. The tag is wrapped in the double quotes an entity-tag requires if it is not already quoted.

func WithMaxAge

func WithMaxAge(d time.Duration) HandlerOption

WithMaxAge sets a Cache-Control max-age on the served document (§7.10). A non-positive duration leaves Cache-Control unset.

type Metadata

type Metadata struct {
	// Resource is the protected resource's resource identifier (§2). REQUIRED.
	// It is an https URL with no fragment.
	Resource string `json:"resource"`

	// AuthorizationServers lists the issuer identifiers of authorization
	// servers that can be used with this protected resource (§2).
	AuthorizationServers []string `json:"authorization_servers,omitempty"`

	// JWKSURI is the URL of the protected resource's JWK Set document (§2).
	JWKSURI string `json:"jwks_uri,omitempty"`

	// ScopesSupported lists the scope values used in authorization requests to
	// access this protected resource (§2). RECOMMENDED.
	ScopesSupported []string `json:"scopes_supported,omitempty"`

	// BearerMethodsSupported lists the supported methods of sending an OAuth 2.0
	// bearer token to the resource (§2): some subset of BearerMethodHeader,
	// BearerMethodBody, and BearerMethodQuery.
	BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"`

	// ResourceSigningAlgValuesSupported lists the JWS "alg" values supported by
	// the resource for signing resource responses (§2).
	ResourceSigningAlgValuesSupported []string `json:"resource_signing_alg_values_supported,omitempty"`

	// ResourceName is a human-readable name of the protected resource intended
	// for display to the end user (§2). RECOMMENDED. Internationalized variants
	// are carried in Extra as "resource_name#<lang>"; read them with Localized.
	ResourceName string `json:"resource_name,omitempty"`

	// ResourceDocumentation is a URL of human-readable developer documentation
	// for the protected resource (§2). Internationalizable; see Localized.
	ResourceDocumentation string `json:"resource_documentation,omitempty"`

	// ResourcePolicyURI is a URL of human-readable information about the
	// resource's requirements on the client (§2). Internationalizable.
	ResourcePolicyURI string `json:"resource_policy_uri,omitempty"`

	// ResourceTOSURI is a URL of the resource's terms of service (§2).
	// Internationalizable.
	ResourceTOSURI string `json:"resource_tos_uri,omitempty"`

	// TLSClientCertificateBoundAccessTokens indicates support for mutual-TLS
	// client-certificate-bound access tokens (§2, RFC 8705). Absent means false.
	TLSClientCertificateBoundAccessTokens bool `json:"tls_client_certificate_bound_access_tokens,omitempty"`

	// AuthorizationDetailsTypesSupported lists the authorization_details type
	// values supported by the resource server (§2, RFC 9396).
	AuthorizationDetailsTypesSupported []string `json:"authorization_details_types_supported,omitempty"`

	// DPoPSigningAlgValuesSupported lists the JWS "alg" values supported by the
	// resource server for validating DPoP proof JWTs (§2, RFC 9449).
	DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"`

	// DPoPBoundAccessTokensRequired indicates that the resource server always
	// requires DPoP-bound access tokens (§2, RFC 9449). Absent means false.
	DPoPBoundAccessTokensRequired bool `json:"dpop_bound_access_tokens_required,omitempty"`

	// SignedMetadata is a JWT (JWS Compact Serialization) whose claims restate
	// metadata parameters about the protected resource (§2.2). This library
	// surfaces it verbatim; verifying its signature is the caller's job.
	SignedMetadata string `json:"signed_metadata,omitempty"`

	// Extra holds members not captured by the typed fields above —
	// service-specific extensions, future registrations, and the §2.1
	// "name#lang" internationalized variants. Values are kept as raw JSON for
	// byte-stable round-trips and zero-cost pass-through.
	Extra map[string]json.RawMessage `json:"-"`
}

Metadata is an RFC 9728 §2 OAuth 2.0 Protected Resource Metadata document.

Resource is the only REQUIRED member; every other field is optional and is omitted from the wire when unset. Service-specific and future-registered members — including the internationalized "name#lang" variants of §2.1 — are preserved verbatim in Extra for byte-stable round-trips.

Decoding is liberal (Postel's law): UnmarshalJSON accepts whatever the wire provides. Strict checks are opt-in via Validate. The resource-match check of §3.3/§3.4 is a fetch-time concern, not part of Validate.

Example (Marshal)
package main

import (
	"encoding/json"
	"fmt"

	prm "github.com/hstern/go-protected-resource-metadata"
)

func main() {
	m := &prm.Metadata{
		Resource:        "https://resource.example.com",
		ScopesSupported: []string{"profile", "email"},
	}
	b, _ := json.Marshal(m)
	fmt.Println(string(b))
}
Output:
{"resource":"https://resource.example.com","scopes_supported":["profile","email"]}

func Fetch

func Fetch(ctx context.Context, c *http.Client, resource string) (*Metadata, error)

Fetch retrieves and validates the protected resource metadata for a resource identifier. It builds the well-known URL with WellKnownPath (§3.1), performs a GET over c (defaulting to http.DefaultClient), decodes the JSON body, and enforces the §3.3/§3.4 anti-mix-up check: the returned resource value MUST be identical to resource. On mismatch it returns ErrResourceMismatch and does not return the document.

The comparison is code-point exact (§6); pass resource in the same canonical form the protected resource publishes. Transport security (TLS per BCP 195) is the responsibility of c.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	prm "github.com/hstern/go-protected-resource-metadata"
)

func main() {
	// A protected resource publishing its metadata at the well-known path.
	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = prm.ServeMetadata(w, &prm.Metadata{
			Resource:        "https://" + r.Host,
			ScopesSupported: []string{"read"},
		})
	}))
	defer srv.Close()

	m, err := prm.Fetch(context.Background(), srv.Client(), srv.URL)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(m.ScopesSupported[0])
}
Output:
read

func FetchMetadataURL

func FetchMetadataURL(ctx context.Context, c *http.Client, metadataURL, expectedResource string) (*Metadata, error)

FetchMetadataURL retrieves and validates a metadata document from a metadata URL obtained out of band — typically the resource_metadata parameter of a WWW-Authenticate challenge (RFC 9728 §5.1). Per §3.3, when the URL came from such a challenge the returned resource value MUST equal the URL the client used to call the resource server; pass that as expectedResource. On mismatch it returns ErrResourceMismatch.

The comparison is code-point exact (§6).

func (*Metadata) GetExtra

func (m *Metadata) GetExtra(name string, v any) (present bool, err error)

GetExtra unmarshals the extension member named name into v, which must be a non-nil pointer. It reports whether the member was present; a missing member is not an error (present == false, err == nil).

Only members not captured by a typed field land in Extra, so GetExtra is the way to read extension members — including the §2.1 "name#lang" variants — byte-for-byte as the server sent them.

func (*Metadata) Handler

func (m *Metadata) Handler(opts ...HandlerOption) http.Handler

Handler returns an http.Handler that serves the metadata document m. It answers GET and HEAD with the JSON document (200) and rejects other methods with 405 and an Allow header. The document is validated and marshaled once, when Handler is called; if m is invalid the handler responds 500 to every request — call m.Validate yourself first if you want to detect that up front.

Register the handler at WellKnownRequestPath(m.Resource):

path, err := prm.WellKnownRequestPath(m.Resource)
// handle err
mux.Handle(path, m.Handler(prm.WithMaxAge(time.Hour)))

Options configure HTTP caching (§7.10): WithMaxAge sets Cache-Control and WithETag sets an ETag and answers a matching If-None-Match with 304.

Example
package main

import (
	"fmt"
	"net/http"
	"time"

	prm "github.com/hstern/go-protected-resource-metadata"
)

func main() {
	m := &prm.Metadata{Resource: "https://resource.example.com"}
	path, _ := prm.WellKnownRequestPath(m.Resource)

	mux := http.NewServeMux()
	mux.Handle(path, m.Handler(prm.WithMaxAge(time.Hour)))
	// http.ListenAndServe(":8080", mux)

	fmt.Println(path)
}
Output:
/.well-known/oauth-protected-resource

func (*Metadata) Localized

func (m *Metadata) Localized(member, tag string) (value string, ok bool)

Localized returns the value of a human-readable member for a requested BCP 47 language tag (RFC 9728 §2.1). member is the member's JSON name, e.g. "resource_name". Localized looks for a tagged variant "member#tag" among the extension members, matching the language tag case-insensitively, and falls back to the untagged member value. ok reports whether any value — tagged or untagged — was found.

An empty tag requests the untagged value directly.

Example
package main

import (
	"encoding/json"
	"fmt"

	prm "github.com/hstern/go-protected-resource-metadata"
)

func main() {
	m := &prm.Metadata{
		ResourceName: "Example Protected Resource",
		Extra: map[string]json.RawMessage{
			"resource_name#fr": json.RawMessage(`"Ressource protégée"`),
		},
	}
	fr, _ := m.Localized("resource_name", "fr")
	de, _ := m.Localized("resource_name", "de") // no "de" variant: untagged fallback
	fmt.Println(fr)
	fmt.Println(de)
}
Output:
Ressource protégée
Example Protected Resource

func (Metadata) MarshalJSON

func (m Metadata) MarshalJSON() ([]byte, error)

MarshalJSON serializes the typed members and merges Extra back in. Typed members win on key collision. Output is byte-stable: with no extension members the typed members serialize in their declared order; with extensions the whole object serializes in encoding/json's sorted-key order. Either way a given Metadata value always marshals to the same bytes.

func (*Metadata) ParseSignedMetadata

func (m *Metadata) ParseSignedMetadata() (*SignedMetadata, error)

ParseSignedMetadata parses m.SignedMetadata (RFC 9728 §2.2) without verifying its signature. It returns (nil, nil) when no signed_metadata is present.

func (*Metadata) SetExtra

func (m *Metadata) SetExtra(name string, v any) error

SetExtra marshals v and stores it as the extension member named name. It returns an error if name collides with a member that has its own typed field — set those through the field instead — or if v cannot be marshalled.

func (*Metadata) UnmarshalJSON

func (m *Metadata) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the typed members and routes every other member of the JSON object into Extra.

func (*Metadata) Validate

func (m *Metadata) Validate() error

Validate reports whether the metadata document satisfies the structural requirements of RFC 9728 §2. It returns the first failure as a *ValidationError (matchable with errors.Is(err, ErrValidation)), or nil.

Validate is document-structural only and is independent of how the document was obtained. The §3.3/§3.4 requirement that a fetched document's resource equal the requested resource identifier is a fetch-time check, not part of Validate.

type SignedMetadata

type SignedMetadata struct {
	// Token is the original compact JWT string.
	Token string
	// Header is the decoded JWS protected header (alg, kid, typ, …).
	Header map[string]json.RawMessage
	// Issuer is the iss claim, REQUIRED by §2.2.
	Issuer string
	// Metadata is the protected resource metadata asserted by the JWT claims.
	Metadata *Metadata
	// SigningInput is the exact byte sequence the signature covers
	// (base64url(header) + "." + base64url(payload)), for the caller's verifier.
	SigningInput []byte
	// Signature is the decoded JWS signature bytes.
	Signature []byte
}

SignedMetadata is the parsed — but NOT signature-verified — content of a signed_metadata JWT (RFC 9728 §2.2). The library decodes the JWS Compact Serialization and exposes its parts so the caller can verify the signature with a JOSE library and a key belonging to the issuer; verifying the signature is deliberately out of scope (the boundary the rest of the suite draws for JOSE).

func ParseSignedMetadata

func ParseSignedMetadata(token string) (*SignedMetadata, error)

ParseSignedMetadata parses a signed_metadata JWT (RFC 9728 §2.2) WITHOUT verifying its signature. It decodes the protected header, the metadata claims, and the iss claim, and exposes the signing input and signature for the caller to verify. It returns an error wrapping ErrInvalidSignedMetadata if token is not a three-part JWS Compact Serialization, if any part is not valid base64url/JSON, if the required iss claim is missing, or if the claims nest a signed_metadata of their own (§2.2 RECOMMENDED rejection).

Verifying the signature against a trusted issuer key is the caller's responsibility; only after that should the signed values be trusted (see SignedMetadata.Apply for the §2.2 precedence merge).

func (*SignedMetadata) Apply

func (sm *SignedMetadata) Apply(base *Metadata) (*Metadata, error)

Apply returns a copy of base with the signed metadata's members overlaid, the signed values taking precedence on a per-member basis as RFC 9728 §2.2 requires. base is not modified. A member the signed metadata does not carry is left as base has it.

Call Apply only AFTER verifying the signature (SigningInput, Signature, Header) against a key belonging to a trusted Issuer. Applying unverified signed values would defeat the point of signing them.

type ValidationError

type ValidationError struct {
	// Field is the member at fault, e.g. "resource".
	Field string
	// Message explains the problem, lowercase and without trailing punctuation.
	Message string
}

ValidationError reports a metadata member that does not satisfy an RFC 9728 requirement — a missing required parameter, a non-https resource identifier, an unsupported bearer method, and the like. It names the offending member so a caller can report a precise problem.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Directories

Path Synopsis
internal
specfixtures
Package specfixtures holds spec-derived conformance vectors for RFC 9728.
Package specfixtures holds spec-derived conformance vectors for RFC 9728.

Jump to

Keyboard shortcuts

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