Documentation
¶
Overview ¶
Package client implements the FAPI 2.0 relying-party (RP) role: the public API a client application uses to drive an authorization-code flow against a FAPI-conformant authorization server.
The package exposes workflow methods (BeginAuthorization, HandleAuthorizationResponse, ExchangeCode, CompleteAuthorization) rather than low-level JWT, PAR or DPoP primitives — those live under internal/ and are composed here behind a state machine that a caller cannot drive out of order. In particular, only this package may construct request objects and PAR submissions; verifying them is the server package's responsibility.
FetchUserInfo, VerifyIssuerJWS and ProtectedResource (via ResourceClient.Do) are the deliberate exceptions: a caller that reaches a protected resource beyond token issuance (the UserInfo endpoint, most commonly, which FetchUserInfo covers directly) needs both to sign a DPoP proof bound to that request and to check something else the authorization server signed, and the alternative — forcing that caller to hand-roll its own DPoP proof construction and to re-fetch and re-parse the issuer's JWKS on its own — is exactly the unhardened, uncoordinated-cache duplication this package exists to avoid. PublicJWKS is the same idea applied to publishing this client's own keys — the mirror image of server.PublicJWKS — so an embedder registering with an authorization server (out of band; this package has no dynamic client registration flow) doesn't have to hand-roll RFC 7517 JWK encoding for whatever it configured Dependencies.Keys/Dependencies.Decryption with.
client must not import server. Where both roles need the same wire format or cryptographic operation, that logic belongs in internal/ and is used asymmetrically (e.g. internal/jarm verifies here, but signs in server; internal/requestobject signs here, but verifies in server).
It follows the same hardening rules as server and resource (see ARCHITECTURE.md, "Hardening rules for every role's public API"): AuthorizationSession and SessionHandle are opaque with no public constructor; HandleAuthorizationResponse returns a closed sum type rather than one struct with optional fields, so a caller can't assume every callback carries a code; every DPoP proof, request-object signature and client assertion is produced through Dependencies.Keys' operation-based Sign, keyed by purpose (ClientAuthentication, RequestObjectSigning, DPoPProofSigning) — this package never constructs, holds or is handed a crypto.PrivateKey, the same model server uses for its own signing keys; TokenSet fields that carry raw token values use fapi.Secret so they can't leak into a log line by accident; and a validation failure is a typed Error tagged with where it's safe to expose the description, not a bare error the caller has to string-match.
Index ¶
- type Algorithms
- type AuthorizationCallback
- type AuthorizationSession
- type BeginAuthorizationRequest
- type CallbackDenied
- type CallbackResult
- type CallbackSuccess
- type Client
- func (c *Client) BeginAuthorization(ctx context.Context, req BeginAuthorizationRequest) (AuthorizationSession, error)
- func (c *Client) CompleteAuthorization(ctx context.Context, cb AuthorizationCallback) (CompletionResult, error)
- func (c *Client) ExchangeCode(ctx context.Context, resp ValidatedAuthorizationResponse) (TokenSet, error)
- func (c *Client) FetchUserInfo(ctx context.Context, tokens TokenSet) (UserInfo, error)
- func (c *Client) HandleAuthorizationResponse(ctx context.Context, cb AuthorizationCallback) (CallbackResult, error)
- func (c *Client) ProtectedResource(tokens TokenSet) *ResourceClient
- func (c *Client) PublicJWKS(ctx context.Context) (PublicKeySet, error)
- func (c *Client) VerifyIssuerJWS(ctx context.Context, compactJWS string) ([]byte, error)
- type Clock
- type CompletionDenied
- type CompletionResult
- type CompletionSuccess
- type Config
- type Dependencies
- type DiscoveredMetadata
- type Endpoints
- type Error
- type ErrorCode
- type IDTokenClaims
- type Limits
- type Profile
- type PublicJWK
- type PublicKeySet
- type ResourceClient
- type SessionHandle
- type SystemClock
- type TokenSet
- type UserInfo
- type ValidatedAuthorizationResponse
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Algorithms ¶
type Algorithms struct {
// ClientAuthentication is the algorithm this client signs its
// private_key_jwt client assertions with.
ClientAuthentication fapi.SignatureAlgorithm
// RequestObject is the algorithm this client signs pushed
// authorization request objects with. Required only when Profile is
// ProfileFAPISecurityWithMessageSigning.
RequestObject fapi.SignatureAlgorithm
// DPoP is the algorithm this client signs DPoP proofs with.
DPoP fapi.SignatureAlgorithm
// JARM is the algorithm the authorization server is registered (or
// discovered) to sign authorization responses with. Required only
// when Profile is ProfileFAPISecurityWithMessageSigning.
JARM fapi.SignatureAlgorithm
// IDToken is the algorithm the authorization server is registered (or
// discovered) to sign ID tokens with.
IDToken fapi.SignatureAlgorithm
// IDTokenKeyManagement, if set, declares that this client expects
// (and can decrypt) an encrypted — or encrypted-then-signed nested
// JWT (OIDC Core §10.2) — ID token, using this key-management
// algorithm. Zero (the default) means this client expects an
// ordinary signed-only ID token; a plain signed ID token arriving
// when this is set is rejected outright, not silently accepted,
// since the whole point of registering for encryption is that a
// downgrade to plaintext must not go unnoticed. Whatever value is
// set here reflects a registration this client made with the
// authorization server out of band — this module has no dynamic
// client registration flow of its own — and Dependencies must
// supply a matching keys.Decrypter when it is non-zero.
IDTokenKeyManagement fapi.KeyManagementAlgorithm
// IDTokenContentEncryption is the content-encryption algorithm
// paired with IDTokenKeyManagement. Required together: both zero, or
// both set. Kept as its own field — separate from
// IDTokenKeyManagement — so a future second
// fapi.ContentEncryptionAlgorithm value doesn't change this field's
// meaning or Config's shape, the same reasoning
// jwe.EncryptRequest/DecryptRequest already apply.
IDTokenContentEncryption fapi.ContentEncryptionAlgorithm
// UserInfo is the algorithm the authorization server is registered
// (or discovered) to sign an issuer-verified JWS with, for
// VerifyIssuerJWS — most commonly the inner JWS of a signed (or
// signed-then-encrypted) UserInfo response (OIDC Core §5.3.2).
// Required to call VerifyIssuerJWS or FetchUserInfo; a client that
// never verifies such an artifact can leave this zero.
UserInfo fapi.SignatureAlgorithm
// UserInfoKeyManagement, if set, declares that FetchUserInfo expects
// (and can decrypt) a signed-then-encrypted nested JWT UserInfo
// response (OIDC Core §5.3.2), using this key-management algorithm.
// Zero (the default) means FetchUserInfo expects a plain JSON or
// signed-only response; a response arriving encrypted when this is
// zero — or unencrypted when this is set — is rejected outright, the
// same downgrade protection Algorithms.IDTokenKeyManagement applies.
// Whatever value is set here reflects a registration this client
// made with the authorization server out of band, and Dependencies
// must supply a matching keys.Decrypter when it is non-zero.
UserInfoKeyManagement fapi.KeyManagementAlgorithm
// UserInfoContentEncryption is the content-encryption algorithm
// paired with UserInfoKeyManagement. Required together: both zero,
// or both set.
UserInfoContentEncryption fapi.ContentEncryptionAlgorithm
}
Algorithms are the single algorithm this client uses for each signing operation it performs, and the single algorithm it expects the authorization server to use for each of its own. A closed fapi.SignatureAlgorithm value, never a caller-suppliable string — see ARCHITECTURE.md design rule 2.
type AuthorizationCallback ¶
type AuthorizationCallback struct {
RawQuery string
}
AuthorizationCallback is the input to Client.HandleAuthorizationResponse — the raw query string of the request the user agent's redirect back to RedirectURI carried. Passing the raw string (rather than a pre-parsed url.Values) means this package — not an HTTP adapter — detects a duplicated parameter, the same raw-request-boundary discipline server's FormRequest applies; see internal/par.DecodeForm.
type AuthorizationSession ¶
type AuthorizationSession struct {
// contains filtered or unexported fields
}
AuthorizationSession is returned by BeginAuthorization: the browser URL to redirect the user agent to, and an opaque handle for the caller's own correlation purposes.
func (AuthorizationSession) Handle ¶
func (s AuthorizationSession) Handle() SessionHandle
Handle is this session's opaque correlation handle.
func (AuthorizationSession) URL ¶
func (s AuthorizationSession) URL() fapi.URL
URL is the authorization URL to redirect the user agent to.
type BeginAuthorizationRequest ¶
type BeginAuthorizationRequest struct {
Scope []string
// ACRValues optionally requests specific Authentication Context
// Class Reference values (OIDC Core §3.1.2.1), most-preferred first.
// Sent as the space-separated "acr_values" parameter only when
// non-empty — many authorization servers reject it outright for a
// client that hasn't been specifically provisioned for it, so this
// is opt-in, never sent by default.
ACRValues []string
// Extensions carries any custom authorization parameters to attach
// to this request — set via extension.Set(&req.Extensions,
// Definition, value). A value whose encoded JSON shape is a bare
// string is sent as a plain top-level authorization/PAR parameter
// regardless of Config.Profile. Any other shape (object, array,
// number, bool) requires
// Config.Profile == ProfileFAPISecurityWithMessageSigning: a signed
// request object carries a value's native JSON shape losslessly,
// while a plain top-level parameter has no way to represent anything
// but a bare string. A non-string value under the baseline profile
// is rejected rather than mis-encoded.
Extensions extension.Values
}
BeginAuthorizationRequest is the input to Client.BeginAuthorization.
type CallbackDenied ¶
CallbackDenied means the authorization server (or the resource owner, via the authorization server) declined the request. Code and Description come from the authorization server's own error response and are safe to surface to the embedding application.
type CallbackResult ¶
type CallbackResult interface {
// contains filtered or unexported methods
}
CallbackResult is a closed sum type returned by HandleAuthorizationResponse, so a caller can't assume every callback carries a code and can't forget to branch on the error case.
type CallbackSuccess ¶
type CallbackSuccess struct {
Response ValidatedAuthorizationResponse
}
CallbackSuccess means the authorization server granted the request. Response must be passed to ExchangeCode to complete the flow.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a FAPI 2.0 relying-party engine. It is entirely unexported — construct one with New.
func New ¶
func New(cfg Config, deps Dependencies) (*Client, error)
New validates cfg and deps and returns a Client. Construction fails unless every configuration value and dependency Config.Profile requires is present and valid.
func (*Client) BeginAuthorization ¶
func (c *Client) BeginAuthorization(ctx context.Context, req BeginAuthorizationRequest) (AuthorizationSession, error)
BeginAuthorization starts a new authorization attempt: it generates state, nonce and a PKCE verifier, builds and signs a request object when Config.Profile requires one, authenticates to and calls the pushed-authorization-request endpoint (RFC 9126), and persists correlation state for the eventual callback.
func (*Client) CompleteAuthorization ¶
func (c *Client) CompleteAuthorization(ctx context.Context, cb AuthorizationCallback) (CompletionResult, error)
CompleteAuthorization validates cb via HandleAuthorizationResponse and, on success, immediately exchanges the resulting code via ExchangeCode — so a caller cannot skip callback validation before exchanging a code (see ARCHITECTURE.md design rule 3).
func (*Client) ExchangeCode ¶
func (c *Client) ExchangeCode(ctx context.Context, resp ValidatedAuthorizationResponse) (TokenSet, error)
ExchangeCode authenticates to the token endpoint, presents a DPoP proof bound to this request, and redeems resp's authorization code for an access token — validating any returned ID token before trusting its subject claim.
func (*Client) FetchUserInfo ¶ added in v0.9.0
FetchUserInfo calls Config.Endpoints.UserInfo with tokens' DPoP-bound access token (via ProtectedResource) and returns the validated claims. The response may be plain JSON, a signed-only JWT, or a signed-then-encrypted nested JWT (OIDC Core §5.3.2) — dispatched on the response's own Content-Type header, never guessed from its shape. A signed component is verified through VerifyIssuerJWS, using the same issuer keys and Config.Algorithms.UserInfo this client already verifies an ID token with; an encrypted component is opened through Dependencies.Decryption under keys.UserInfoDecryption, using Config.Algorithms.UserInfoKeyManagement/UserInfoContentEncryption — exactly the same shape ExchangeCode already applies to an encrypted ID token, reused here rather than reinvented.
tokens must carry a validated ID token (HasIDToken): OIDC Core §5.3.2 requires the UserInfo Response's sub Claim to be verified against the ID Token's sub Claim, to guard against a token-substitution attack — there is nothing to check it against otherwise.
func (*Client) HandleAuthorizationResponse ¶
func (c *Client) HandleAuthorizationResponse(ctx context.Context, cb AuthorizationCallback) (CallbackResult, error)
HandleAuthorizationResponse validates an authorization callback: the "iss" parameter (RFC 9207 §2.4) for a plain-mode response — checked before anything else derived from params is trusted, including an "error" code, since "clients MUST NOT assume that the error originates from the intended authorization server" until iss itself checks out — correlation state (via SessionStore.Consume, which also prevents the same callback being processed twice), issuer, JARM signature and claims when Config.Profile requires one, response-mode consistency with what this session requested, and authorization-code or error-response presence.
func (*Client) ProtectedResource ¶ added in v0.9.0
func (c *Client) ProtectedResource(tokens TokenSet) *ResourceClient
ProtectedResource returns a ResourceClient bound to tokens' access token, ready to make DPoP-bound requests to a protected resource with it — reusing the same DPoP key ExchangeCode originally bound the token to, so every proof's JWK thumbprint keeps matching the token's own cnf.jkt automatically, without this package ever exposing that key to the caller.
func (*Client) PublicJWKS ¶ added in v0.12.0
func (c *Client) PublicJWKS(ctx context.Context) (PublicKeySet, error)
PublicJWKS returns this client's current public keys: its ClientAuthentication signing key (always — every profile this module supports authenticates via private_key_jwt), its RequestObjectSigning key (only under ProfileFAPISecurityWithMessageSigning), and its IDTokenDecryption/UserInfoDecryption encryption key(s) — whichever of Config.Algorithms.IDTokenKeyManagement/UserInfoKeyManagement are set — deduplicated by kid. DPoPProofSigning is deliberately excluded: RFC 9449 embeds a DPoP proof's public key directly in the proof's own "jwk" header, never via a discoverable JWKS, so there is nothing to publish for it here.
If Dependencies.Keys implements keys.RotatingKeyManager, every key it reports as currently valid for a signing purpose is published — not just the one Sign currently uses — so a signature made just before a key rotation's cutover stays verifiable through the rotation's overlap window, the same as server.PublicJWKS already does.
func (*Client) VerifyIssuerJWS ¶ added in v0.9.0
VerifyIssuerJWS verifies compactJWS as a compact JWS produced by this client's configured authorization server, resolved through the same cached, rotation-aware Dependencies.IssuerKeys the client itself uses to verify an ID token — so a caller never needs a second, unhardened key fetch to check an issuer-signed artifact beyond the ID token (a UserInfo response, most commonly). It checks the signature against Config.Algorithms.UserInfo, never the JWS header's own "alg" — this module treats a token's own algorithm header as untrusted input everywhere else, and this call is no exception — and returns the verified payload bytes unparsed: VerifyIssuerJWS makes no claim about what's inside beyond "the issuer signed exactly these bytes". A caller still owns checking whatever claims that payload carries (iss, aud, sub, expiry, ...) against its own policy.
type Clock ¶
Clock supplies the current time. There is no implicit default — Dependencies.Clock must always be set explicitly, even to SystemClock.
type CompletionDenied ¶
CompletionDenied means the authorization server (or the resource owner, via the authorization server) declined the request.
type CompletionResult ¶
type CompletionResult interface {
// contains filtered or unexported methods
}
CompletionResult is a closed sum type returned by CompleteAuthorization.
type CompletionSuccess ¶
type CompletionSuccess struct {
Tokens TokenSet
}
CompletionSuccess means the flow completed and Tokens were issued.
type Config ¶
type Config struct {
// Issuer is the authorization server this client talks to — the
// audience client assertions and request objects are addressed to,
// and the issuer authorization responses and ID tokens must come
// from.
Issuer fapi.URL
ClientID fapi.ClientID
// RedirectURI is this client's registered redirect URI, sent on every
// authorization request exactly as registered.
RedirectURI string
Endpoints Endpoints
Profile Profile
Algorithms Algorithms
Limits Limits
// RequireAuthorizationResponseIss makes HandleAuthorizationResponse
// reject a callback with no "iss" parameter at all, rather than only
// checking one that's present. RFC 9207 §2.4 mandates both halves
// unconditionally regardless of this setting — a present-but-wrong
// "iss" is always rejected — but "MUST reject authorization responses
// without the iss parameter" applies only "from authorization servers
// that do support the parameter", which this client can't determine
// on its own; set this from
// DiscoveredMetadata.AuthorizationResponseIssSupported (or a
// deployment's own out-of-band knowledge of the server).
RequireAuthorizationResponseIss bool
// TrustedIDTokenAudiences lists any other party this client trusts
// to also be named alongside its own ClientID in a multi-valued
// "aud" claim on a received ID token. OIDC Core §3.1.3.7 step 3
// requires rejecting an ID token "if it contains additional
// audiences not trusted by the Client" — by default (nil/empty)
// this client trusts none, so any audience besides its own ClientID
// causes rejection, exactly as before this field existed. Set only
// to entries this client has an actual, specific reason to trust;
// see internal/token.IDTokenValidatePolicy.TrustedAudiences, which
// this configures.
TrustedIDTokenAudiences []string
// TolerateUserInfoSubjectEqualsClientID works around a defect some
// authorization servers have been observed to have: returning the
// client's own client_id as the UserInfo response's "sub" claim,
// instead of the authenticated end-user's actual subject identifier.
// OIDC Core §5.3.2 requires this client to reject a UserInfo
// response whose "sub" doesn't exactly match the ID token's "sub" —
// the defense against a resource server (or a network attacker in
// front of it) substituting another user's claims into a response
// this flow would otherwise trust. Setting this to true additionally
// accepts "sub" equal to this client's own ClientID as a fallback
// when it doesn't match the ID token's subject.
//
// Enable this only against a specific authorization server you've
// confirmed has this defect, and only until it's fixed server-side:
// every one of that server's UserInfo responses now carries the same
// "sub" value (the client_id) regardless of which end-user actually
// authenticated, so this check can no longer distinguish one user's
// claims from another's for that server — the exact substitution
// OIDC Core §5.3.2 exists to catch. Defaults to false, which
// preserves the exact-match behavior this package has always had.
TolerateUserInfoSubjectEqualsClientID bool
}
Config is this client's immutable configuration. It is copied by New; mutating a Config after passing it to New has no effect.
type Dependencies ¶
type Dependencies struct {
// Sessions persists in-progress authorization-flow state.
Sessions storage.SessionStore
// Keys performs this client's own signing operations: client
// assertion signing, request-object signing (when Config.Profile
// requires it), and DPoP proof signing.
Keys keys.KeyManager
// IssuerKeys resolves the authorization server's verification keys,
// to verify a JARM response (when Config.Profile requires one) and an
// issued ID token.
IssuerKeys keys.IssuerKeySource
// HTTP performs this client's PAR and token-endpoint calls.
HTTP fapihttp.HTTPClient
// Clock supplies the current time.
Clock Clock
// Random is the source of randomness for state, nonce and PKCE
// verifier generation.
Random io.Reader
// Decryption recovers the content-encryption key of an encrypted ID
// token, using the keys.IDTokenDecryption purpose. Required exactly
// when Config.Algorithms.IDTokenKeyManagement is set; nil otherwise
// — most deployments never register for encrypted ID tokens, so
// this stays an opt-in dependency rather than a mandatory one every
// embedder has to wire up.
Decryption keys.Decrypter
}
Dependencies are this client's injected collaborators. New rejects a nil value for any field — there is no implicit fallback (no default clock, no silently-installed in-memory session store).
type DiscoveredMetadata ¶
type DiscoveredMetadata struct {
// Endpoints.UserInfo is the server's advertised UserInfo Endpoint
// (OpenID Connect Discovery 1.0 §3), if any — OPTIONAL, so a zero
// fapi.URL means the server didn't advertise one; FetchUserInfo is
// then unavailable until a caller sets it from its own out-of-band
// knowledge of the server. Populated here, alongside every other
// endpoint, precisely so assigning this whole struct to Config's own
// Endpoints field (the pattern every other endpoint already expects)
// is enough on its own — a caller doesn't need to separately notice
// and hand-copy a UserInfo-specific field the way an earlier version
// of this struct required.
Endpoints Endpoints
JWKSURI fapi.URL
IDTokenAlgorithms []fapi.SignatureAlgorithm
RequestObjectAlgorithms []fapi.SignatureAlgorithm
JARMAlgorithms []fapi.SignatureAlgorithm
// IDTokenEncryptionAlgorithms/IDTokenEncryptionEncValues are every
// key-management/content-encryption algorithm this module
// recognizes among what the server advertised for encrypted ID
// tokens (OIDC Core §10.2). Both empty means the server never
// advertised encryption support at all — most servers. Populating
// Config.Algorithms.IDTokenKeyManagement/IDTokenContentEncryption
// from a value not present here would mean registering for
// encryption the server never said it could do; this module leaves
// that check to the caller; it does not enforce it here.
IDTokenEncryptionAlgorithms []fapi.KeyManagementAlgorithm
IDTokenEncryptionEncValues []fapi.ContentEncryptionAlgorithm
// RequireSignedRequestObject reflects the server's own
// require_signed_request_object metadata value — a caller targeting
// that server should set Config.Profile to
// ProfileFAPISecurityWithMessageSigning accordingly.
RequireSignedRequestObject bool
// AuthorizationResponseIssSupported reflects the server's own
// authorization_response_iss_parameter_supported metadata value — a
// caller targeting that server should set
// Config.RequireAuthorizationResponseIss accordingly (RFC 9207 §2.4:
// "Clients MUST reject authorization responses without the iss
// parameter from authorization servers that do support the
// parameter").
AuthorizationResponseIssSupported bool
}
DiscoveredMetadata is what Discover returns: the endpoints and published algorithm support a caller needs to build a Config, already checked against the issuer identifier the caller asked for. It has no public constructor — only Discover produces one — so a caller can't assemble one from untrusted data and mistake it for something that already went through the anti-spoofing issuer check RFC 8414 §3.3 and OpenID Connect Discovery 1.0 §4.3 require.
Discover never picks an algorithm on the caller's behalf — each Algorithms slice is every value this module's closed fapi.SignatureAlgorithm set recognizes among what the server advertised (an authorization server may advertise algorithms this module doesn't implement, e.g. RS256; those are silently omitted, not treated as an error). Choosing which one to actually use for Config is a caller decision.
func Discover ¶
func Discover(ctx context.Context, fetcher *fapihttp.Client, issuer fapi.URL, opts ...fapi.URLOption) (DiscoveredMetadata, error)
Discover fetches and validates issuer's OpenID Connect Discovery document (".well-known/openid-configuration" appended after any path component issuer itself carries — OIDC Discovery 1.0 §4.1's own worked example: issuer "https://example.com/issuer1" resolves to "GET /issuer1/.well-known/openid-configuration". This is deliberately not RFC 8414 §3.1's insert-before-path algorithm — RFC 8414 §5 itself acknowledges "openid-configuration... differs from OpenID Connect Discovery 1.0's approach", and real OIDC deployments (this module's own conformance suite included) serve the "openid-configuration" suffix at the §4.1 location, not the RFC 8414 one) via fetcher, and returns the endpoints and algorithm support a caller needs to build a Config. It does not construct a Client itself: the caller still supplies its own ClientID, RedirectURI, chosen algorithms and Dependencies — Discover only ever removes the need to hand-copy an authorization server's published endpoints and algorithm list.
opts is forwarded to the fapi.URL parse of every discovered endpoint — pass fapi.AllowLoopbackHTTP() for a local development authorization server, exactly as when parsing an endpoint URL by hand.
type Endpoints ¶
type Endpoints struct {
Authorization fapi.URL
Token fapi.URL
PushedAuthorizationRequest fapi.URL
// UserInfo is the server's UserInfo Endpoint (OpenID Connect
// Discovery 1.0 §3), if this client calls FetchUserInfo. OPTIONAL —
// zero means FetchUserInfo is unavailable (it returns an error
// rather than assuming any particular URL). Discover populates this
// automatically when the server advertises one; otherwise, set it
// from a deployment's own out-of-band knowledge of the server.
UserInfo fapi.URL
}
Endpoints are the authorization server's endpoint URLs this client calls or redirects the user agent to.
type Error ¶
type Error struct {
// contains filtered or unexported fields
}
Error is the error type every public Client method returns. Code and PublicDescription are safe to surface to the embedding application (e.g. in a UI message); the underlying cause (available via Unwrap, and included in Error's own message) is for logs only.
func (*Error) Error ¶
Error implements the error interface. Its output includes the internal cause and is meant for logs, not for a user-facing message.
func (*Error) PublicDescription ¶
PublicDescription returns a short, safe-to-expose description.
type ErrorCode ¶
type ErrorCode string
ErrorCode is a closed set of error codes this client's public methods return.
type IDTokenClaims ¶ added in v0.8.0
type IDTokenClaims struct {
Subject string
// AuthTime is the zero time if the token carried no auth_time claim.
AuthTime time.Time
ACR string
AMR []string
Parameters map[string]json.RawMessage
ExpiresAt time.Time
}
IDTokenClaims is the validated set of standard ID token claims beyond the bare Subject — every value here has already been checked against Config's issuer, audience, algorithm, nonce and clock-skew policy, exactly like Subject. Parameters holds every other claim the token carried (custom identity claims, extension claims, etc.) — anything not already surfaced as one of the named fields or implicitly verified (iss, aud, exp, iat, nonce).
For an encrypted ID token, this is the only way to reach anything beyond Subject at all: decryption happens entirely inside client, so an embedding application has no way to recover the plaintext (and therefore no other claim) on its own — Dependencies.Decryption may be backed by an HSM or remote service that never exposes decrypted bytes outside this call.
type Limits ¶
type Limits struct {
// ClientAssertionLifetime is how long a client assertion this client
// signs remains valid for (exp = Now + ClientAssertionLifetime).
ClientAssertionLifetime time.Duration
// RequestObjectLifetime is how long a signed request object this
// client produces remains valid for. Required only when Profile is
// ProfileFAPISecurityWithMessageSigning.
RequestObjectLifetime time.Duration
// SessionLifetime bounds how long between BeginAuthorization and a
// completed HandleAuthorizationResponse a session remains valid.
SessionLifetime time.Duration
// MaxJARMResponseLifetime bounds how far in the future a JARM
// response's exp claim may be. Required only when Profile is
// ProfileFAPISecurityWithMessageSigning.
MaxJARMResponseLifetime time.Duration
// MaxIDTokenLifetime bounds how far in the future an ID token's exp
// claim may be.
MaxIDTokenLifetime time.Duration
// MaxClockSkew bounds how far in the future an iat/nbf claim may be,
// and extends how long past exp an artifact is still accepted. Zero
// means no tolerance.
MaxClockSkew time.Duration
// HTTPTimeout bounds how long a single PAR or token-endpoint call may
// take.
HTTPTimeout time.Duration
// MaxHTTPResponseBytes bounds how much of a PAR or token-endpoint
// response body this client reads before failing.
MaxHTTPResponseBytes int64
}
Limits bounds the lifetimes and clock tolerances this client enforces or sets. None of these have an implicit default — New rejects a zero (or, for MaxClockSkew, negative) value.
type Profile ¶
type Profile uint8
Profile selects which FAPI 2.0 security profile this client targets. It must match the authorization server's own configured profile — see server.Profile.
const ( // ProfileFAPISecurity is the FAPI 2.0 Security Profile baseline: PAR, // PKCE and DPoP are always used; the pushed authorization request // carries plain parameters, and the authorization response is plain // query parameters. ProfileFAPISecurity Profile // ProfileFAPISecurityWithMessageSigning additionally signs every // pushed authorization request as a request object, and requires the // authorization response to be a signed JARM response. ProfileFAPISecurityWithMessageSigning )
type PublicJWK ¶ added in v0.12.0
type PublicJWK struct {
// contains filtered or unexported fields
}
PublicJWK is one of this client's public keys, in JWK format (RFC 7517). Its only exported surface is KeyID and MarshalJSON — there is no exported way to construct one, and no way to extract the private key it corresponds to, because there never was one available to this package: Dependencies.Keys/Dependencies.Decryption each hand back a public key only.
func (PublicJWK) MarshalJSON ¶ added in v0.12.0
MarshalJSON encodes k as a JWK JSON object.
type PublicKeySet ¶ added in v0.12.0
type PublicKeySet struct {
Keys []PublicJWK `json:"keys"`
}
PublicKeySet is a JWK Set (RFC 7517 §5) of this client's own public keys. This module has no dynamic client registration flow of its own, so publishing the result — at whatever jwks_uri (or static jwks value) was registered with the authorization server out of band — stays this integrator's own concern, the same way server.PublicJWKS's result is never itself served by that package either.
type ResourceClient ¶ added in v0.9.0
type ResourceClient struct {
// contains filtered or unexported fields
}
ResourceClient performs DPoP-bound requests to a protected resource with one already-issued access token — most commonly the OIDC UserInfo endpoint, but any FAPI 2.0 protected resource sender- constrained to the same token follows the identical shape (RFC 9449 §4-§9): attach the token, prove possession of the DPoP key it's bound to, and retry once if the resource server challenges for a fresh nonce. It has no public constructor — only Client.ProtectedResource produces one, always scoped to a specific TokenSet, so a caller can't assemble one detached from an actual access token.
func (*ResourceClient) Do ¶ added in v0.9.0
Do performs req as a DPoP-bound request: it sets "Authorization: DPoP <token>" and a fresh signed DPoP proof (RFC 9449 §4, with "ath" bound to the access token per §4.3), bounded by Config.Limits.HTTPTimeout and Config.Limits.MaxHTTPResponseBytes — the same protections this client applies to its own PAR and token-endpoint calls. If the resource server challenges with a fresh nonce (RFC 9449 §9: HTTP 401, a WWW-Authenticate: DPoP challenge naming use_dpop_nonce, and a DPoP-Nonce header), Do retries exactly once with that nonce echoed into a fresh proof; any other response — including a second nonce challenge — is returned as-is for the caller to interpret. As with the token endpoint's own nonce retry, the DPoP-Nonce header alone is not sufficient grounds to retry: a resource server may send it unprompted to pre-seed a caller's next request, so the retry is gated on the WWW-Authenticate challenge itself too.
req's body, if any, must be replayable — Do may send it twice — so req.GetBody must be set; http.NewRequestWithContext sets it automatically for the body types it accepts (e.g. *bytes.Reader, *strings.Reader). req's own context is replaced with one bounded by Config.Limits.HTTPTimeout.
type SessionHandle ¶
type SessionHandle struct {
// contains filtered or unexported fields
}
SessionHandle identifies one in-progress authorization attempt. It has no public constructor — only BeginAuthorization can produce one. This package never requires it back as input (HandleAuthorizationResponse looks a session up by the "state" value the callback itself carries), but the caller may use it for its own correlation purposes — e.g. binding the pre-authorization request to the eventual callback with an HTTP-only cookie, as defense in depth alongside "state".
func (SessionHandle) String ¶
func (h SessionHandle) String() string
String returns the handle's opaque wire value.
type SystemClock ¶
type SystemClock struct{}
SystemClock is a Clock backed by time.Now. It is a plain, ready-made implementation a caller can wire in explicitly — supplying it is still a deliberate choice, not an implicit fallback New applies on its own.
type TokenSet ¶
type TokenSet struct {
AccessToken fapi.Secret
TokenType string // always "DPoP", regardless of the casing the server responded with (RFC 6749 §7.1: token_type is case insensitive)
Scope string
// ExpiresIn is set only when the token response actually carried
// expires_in — RFC 6749 §5.1 marks it RECOMMENDED, not REQUIRED, and
// explicitly permits an authorization server to omit it and
// communicate the token's lifetime "via other means" instead. Zero
// does not mean "expires immediately"; check HasExpiresIn first.
ExpiresIn time.Duration
HasExpiresIn bool
// IDToken, Subject and IDTokenClaims are set only when the granted
// scope included "openid". Both Subject and IDTokenClaims come from
// the same validated ID token, never from an unverified claim.
// Subject is kept as its own field for backward compatibility;
// IDTokenClaims.Subject carries the identical value.
IDToken fapi.Secret
HasIDToken bool
Subject string
IDTokenClaims IDTokenClaims
// RefreshToken is set only when the authorization server issued one.
RefreshToken fapi.Secret
HasRefreshToken bool
}
TokenSet is returned by a successful ExchangeCode.
type UserInfo ¶ added in v0.9.0
type UserInfo struct {
Subject string
Parameters map[string]json.RawMessage
}
UserInfo is the validated set of OIDC UserInfo claims (OIDC Core §5.3). Subject is always the ID token's own already-verified subject, not necessarily the UserInfo response's own sub claim verbatim — the two normally carry the same value (FetchUserInfo has already checked that), but see Config.TolerateUserInfoSubjectEqualsClientID for the one narrow case where they're allowed to differ, in which this is still the trusted value, never the client_id. Parameters holds every other claim the response carried (profile-defined claims such as name or email, or any deployment-specific extension claim).
type ValidatedAuthorizationResponse ¶
type ValidatedAuthorizationResponse struct {
// contains filtered or unexported fields
}
ValidatedAuthorizationResponse is what HandleAuthorizationResponse returns for a successful callback. It is opaque and can only be constructed by HandleAuthorizationResponse — the only way to obtain one is to have already passed every check HandleAuthorizationResponse performs, so ExchangeCode can never be called with an unvalidated code.