Documentation
¶
Overview ¶
Package storage defines the persistence contracts used by client and server, plus the replay-detection primitive they can both safely share.
Client and server state have different semantics and must not be collapsed into one generic CRUD interface: client_repository.go defines the server's registered-client lookup, transaction.go defines the server's PAR transaction store — CreatePAR persists a pushed authorization request, BeginAuthorization atomically retrieves and consumes one and associates it with a new interaction handle, and CompleteAuthorization atomically retrieves and consumes that interaction — grant.go defines the server's authorization-code and refresh-token store — CreateAuthorizationCode/CreateRefreshToken each persist one, RedeemAuthorizationCode/RedeemRefreshToken each atomically retrieve and consume one (refresh tokens rotate: every redemption is paired with a new CreateRefreshToken call) — access_token.go defines AccessTokenStore, the storage-backed alternative to a self-contained JWT access token (CreateAccessToken/LookupAccessToken only — existence and expiry, never revocation, see that file's own doc comment for why) — and replay.go defines a single-use ReplayStore keyed by a namespaced identifier (e.g. "client:jarm", "server:dpop") so that different roles and subsystems can never collide on the same use-once token. The client's own session store will follow the same per-role-type pattern once the endpoint that needs it exists. No interface here exposes GetX/UpdateX/DeleteX-style CRUD — every method is a named security operation (Create, Consume, Redeem, UseOnce), and redemption-style operations verify and consume state atomically in one call rather than as separate check-then-act steps. replay.Store persists only a digest and expiry per use, never a complete client assertion or DPoP proof.
Because a backend's atomicity/durability guarantees are self-asserted, this package also defines a StoreAssurance.Capabilities interface (durable, atomic-consume, serializable-redemption, cross-instance-consistent, encrypted-at-rest) that server checks at construction time under its production assurance level, plus a reusable contract test suite (e.g. TestGrantStoreContract(t, factory)) that exercises single-use redemption and its exactly-one-winner behavior under in-process concurrency, field round-tripping, unknown-key handling, and revocation, against any implementation — first-party or downstream. The suite runs one store instance in one process against context.Background(); it deliberately does not verify cross-instance/cross-connection atomicity, ExpiresAt-driven eviction, context-cancellation, or that the store deep-copies caller-supplied slices/maps. A production backend must establish those separately — see StoreAssurance.Capabilities for the atomicity/durability properties server requires under its production assurance level.
Index ¶
- func TestAccessTokenStoreContract(t *testing.T, factory func() AccessTokenStore)
- func TestBackchannelAuthenticationStoreContract(t *testing.T, factory func() BackchannelAuthenticationStore)
- func TestGrantStoreContract(t *testing.T, factory func() GrantStore)
- func TestNonceStoreContract(t *testing.T, factory func() NonceStore)
- func TestReplayStoreContract(t *testing.T, factory func() ReplayStore)
- func TestSessionStoreContract(t *testing.T, factory func() SessionStore)
- func TestTransactionStoreContract(t *testing.T, factory func() TransactionStore)
- type AccessTokenLookup
- type AccessTokenStore
- type AuthorizationCodeAlreadyRedeemedError
- type AuthorizationCodeRedemption
- type BackchannelAuthenticationAlreadyRedeemedError
- type BackchannelAuthenticationExpiredError
- type BackchannelAuthenticationSlowDownError
- type BackchannelAuthenticationStatus
- type BackchannelAuthenticationStore
- type BackchannelTokenDeliveryMode
- type BeginAuthorizationTransaction
- type Capabilities
- type ClientAuthMethod
- type ClientRepository
- type CompleteAuthorizationTransaction
- type CompletedInteraction
- type ConsumedSession
- type DecideBackchannelAuthentication
- type DecidedBackchannelAuthentication
- type GrantStore
- type LookedUpAccessToken
- type NewAccessToken
- type NewAuthorizationCode
- type NewBackchannelAuthentication
- type NewPARRecord
- type NewRefreshToken
- type NewSession
- type NonceConsumption
- type NonceIssuance
- type NonceRecord
- type NonceStore
- type PollBackchannelAuthentication
- type PolledBackchannelAuthentication
- type PushedAuthorizationRequest
- type RedeemedAuthorizationCode
- type RedeemedRefreshToken
- type RefreshTokenRedemption
- type RegisteredClient
- func (c RegisteredClient) AllowsScope(scope string) bool
- func (c RegisteredClient) BackchannelAuthenticationRequestAlgorithm() (algorithm fapi.SignatureAlgorithm, permitted bool)
- func (c RegisteredClient) BackchannelClientNotificationEndpoint() fapi.URL
- func (c RegisteredClient) BackchannelTokenDeliveryMode() BackchannelTokenDeliveryMode
- func (c RegisteredClient) ClientAssertionAlgorithm() fapi.SignatureAlgorithm
- func (c RegisteredClient) ClientAuthMethod() ClientAuthMethod
- func (c RegisteredClient) ExpectedCertificateThumbprint() string
- func (c RegisteredClient) ExpectedSubjectDN() string
- func (c RegisteredClient) HasRedirectURI(candidate string) bool
- func (c RegisteredClient) ID() fapi.ClientID
- func (c RegisteredClient) IDTokenEncryption() (keyManagement fapi.KeyManagementAlgorithm, ...)
- func (c RegisteredClient) RequestObjectAlgorithm() (algorithm fapi.SignatureAlgorithm, permitted bool)
- func (c RegisteredClient) SenderConstrain() SenderConstrain
- func (c RegisteredClient) UserInfoEncryption() (keyManagement fapi.KeyManagementAlgorithm, ...)
- type RegisteredClientConfig
- type ReplayNamespace
- type ReplayStore
- type ReplayUse
- type SenderConstrain
- type SessionConsumption
- type SessionStore
- type StoreAssurance
- type TransactionStore
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func TestAccessTokenStoreContract ¶
func TestAccessTokenStoreContract(t *testing.T, factory func() AccessTokenStore)
TestAccessTokenStoreContract exercises factory()'s behavior against the guarantees AccessTokenStore's documentation promises: a created token's fields round-trip faithfully through LookupAccessToken, and an unknown hash is rejected. factory must return a fresh, empty AccessTokenStore each call.
func TestBackchannelAuthenticationStoreContract ¶ added in v0.18.0
func TestBackchannelAuthenticationStoreContract(t *testing.T, factory func() BackchannelAuthenticationStore)
TestBackchannelAuthenticationStoreContract exercises factory()'s behavior against the guarantees BackchannelAuthenticationStore's documentation promises: DecideBackchannelAuthentication is single-use; PollBackchannelAuthentication is reusable for Pending/Denied/AuthenticationFailed but single-use for the first Approved observation (mirroring RedeemRefreshToken's and RedeemAuthorizationCode's contracts respectively, layered on one interface); expiry and slow-down are enforced. factory must return a fresh, empty BackchannelAuthenticationStore each call.
func TestGrantStoreContract ¶
func TestGrantStoreContract(t *testing.T, factory func() GrantStore)
TestGrantStoreContract exercises factory()'s behavior against the guarantees GrantStore's documentation promises: atomic single-use redemption of authorization codes and refresh tokens, faithful round-tripping of stored fields, and exactly one winner under concurrent redemption of the same code or token. factory must return a fresh, empty GrantStore each call — its subtests share nothing between them.
func TestNonceStoreContract ¶ added in v0.16.0
func TestNonceStoreContract(t *testing.T, factory func() NonceStore)
TestNonceStoreContract exercises factory()'s behavior against the guarantees NonceStore's documentation promises: an issued nonce is atomically single-use, an unknown nonce is rejected, and concurrent consumption of the same nonce has exactly one winner. factory must return a fresh, empty NonceStore each call.
func TestReplayStoreContract ¶
func TestReplayStoreContract(t *testing.T, factory func() ReplayStore)
TestReplayStoreContract exercises factory()'s behavior against the guarantees ReplayStore's documentation promises: a digest is single-use within its namespace, the same digest in a different namespace never collides, and concurrent uses of the same digest have exactly one winner. factory must return a fresh, empty ReplayStore each call.
func TestSessionStoreContract ¶
func TestSessionStoreContract(t *testing.T, factory func() SessionStore)
TestSessionStoreContract exercises factory()'s behavior against the guarantees SessionStore's documentation promises: a session is atomically single-use by State, stored fields round-trip faithfully, and concurrent consumption of the same State has exactly one winner. factory must return a fresh, empty SessionStore each call.
func TestTransactionStoreContract ¶
func TestTransactionStoreContract(t *testing.T, factory func() TransactionStore)
TestTransactionStoreContract exercises factory()'s behavior against the guarantees TransactionStore's documentation promises: a pushed request_uri and an interaction handle are each atomically single-use, and stored fields round-trip faithfully. factory must return a fresh, empty TransactionStore each call.
Types ¶
type AccessTokenLookup ¶
type AccessTokenLookup struct {
// TokenHash is the SHA-256 digest of the presented access token
// value — the same digest CreateAccessToken stored it under.
TokenHash [32]byte
}
AccessTokenLookup is the input to AccessTokenStore.LookupAccessToken.
type AccessTokenStore ¶
type AccessTokenStore interface {
CreateAccessToken(ctx context.Context, tok NewAccessToken) error
// LookupAccessToken returns the stored record for lookup.TokenHash.
// It returns an error only if the hash is unknown; the caller
// checks the returned record's own expiry (ExpiresAt) itself, the
// same way every other *Redeemed/LookedUp* type in this package is
// checked by its caller.
LookupAccessToken(ctx context.Context, lookup AccessTokenLookup) (LookedUpAccessToken, error)
}
AccessTokenStore persists opaque access tokens — the storage-backed alternative to a self-contained JWT (see server.JWTAccessTokens vs. server.OpaqueAccessTokens, and resource's matching pair). Only relevant when an opaque AccessTokenIssuer/AccessTokenResolver is in use; a deployment issuing JWT access tokens never needs this dependency at all.
Revocation is deliberately not this interface's concern — see server.RevocationSink/resource.RevocationChecker, called separately and uniformly by this library regardless of access-token format, the same way for both JWT and opaque tokens. LookupAccessToken only needs to answer "does this exist and what does it mean" — existence and expiry, nothing else.
type AuthorizationCodeAlreadyRedeemedError ¶
type AuthorizationCodeAlreadyRedeemedError struct {
// IssuedAccessTokenKey is the revocation-lookup key of the access
// token issued on the original (first) redemption (a JWT's jti
// claim, or an opaque token's own hash — see
// server.AccessTokenIssuer.IssueAccessToken) — "" if
// RecordIssuedAccessToken was never called for this code (e.g. no
// revocation support wired in).
IssuedAccessTokenKey string
// IssuedRefreshTokenHash is the hash of the refresh token issued on
// the original redemption, if one was (the authorization included
// "offline_access") and RecordIssuedRefreshToken was called for it
// — nil otherwise.
IssuedRefreshTokenHash *[32]byte
}
AuthorizationCodeAlreadyRedeemedError is what RedeemAuthorizationCode must return (satisfying errors.As) when CodeHash was already consumed — as opposed to any other failure (e.g. an unknown code), which returns a plain error. RFC 6749 §4.1.2: "If an authorization code is used more than once, the authorization server MUST deny the request and SHOULD revoke (if possible) all tokens previously issued based on that authorization code" — "all tokens," so this carries both halves back to the caller, whichever were actually issued and recorded.
func (*AuthorizationCodeAlreadyRedeemedError) Error ¶
func (e *AuthorizationCodeAlreadyRedeemedError) Error() string
type AuthorizationCodeRedemption ¶
type AuthorizationCodeRedemption struct {
// CodeHash is the SHA-256 digest of the presented code value — the
// same digest CreateAuthorizationCode stored it under.
CodeHash [32]byte
}
AuthorizationCodeRedemption is the input to GrantStore.RedeemAuthorizationCode.
type BackchannelAuthenticationAlreadyRedeemedError ¶ added in v0.18.0
type BackchannelAuthenticationAlreadyRedeemedError struct {
IssuedAccessTokenKey string
IssuedRefreshTokenHash *[32]byte
}
BackchannelAuthenticationAlreadyRedeemedError mirrors AuthorizationCodeAlreadyRedeemedError exactly: returned when a poll observes an already-consumed Approved record — a CIBA auth_req_id issues tokens exactly once (CIBA §10.3).
func (*BackchannelAuthenticationAlreadyRedeemedError) Error ¶ added in v0.18.0
func (e *BackchannelAuthenticationAlreadyRedeemedError) Error() string
type BackchannelAuthenticationExpiredError ¶ added in v0.18.0
type BackchannelAuthenticationExpiredError struct{}
BackchannelAuthenticationExpiredError is returned by PollBackchannelAuthentication once the record's own ExpiresAt has passed, regardless of its decision status.
func (*BackchannelAuthenticationExpiredError) Error ¶ added in v0.18.0
func (e *BackchannelAuthenticationExpiredError) Error() string
type BackchannelAuthenticationSlowDownError ¶ added in v0.18.0
type BackchannelAuthenticationSlowDownError struct{}
BackchannelAuthenticationSlowDownError is returned by PollBackchannelAuthentication when called again before the record's own PollInterval has elapsed since the previous poll.
func (*BackchannelAuthenticationSlowDownError) Error ¶ added in v0.18.0
func (e *BackchannelAuthenticationSlowDownError) Error() string
type BackchannelAuthenticationStatus ¶ added in v0.18.0
type BackchannelAuthenticationStatus uint8
BackchannelAuthenticationStatus is the closed set of states a pending CIBA backchannel authentication request can be in.
const ( // BackchannelAuthenticationPending means no decision has been // recorded yet — the end user has not yet approved, denied, or // failed to authenticate out-of-band. BackchannelAuthenticationPending BackchannelAuthenticationStatus // BackchannelAuthenticationApproved means the end user authenticated // and approved the request. BackchannelAuthenticationApproved // BackchannelAuthenticationDenied means the end user (or the // application, on their behalf) declined to authorize the request. BackchannelAuthenticationDenied // BackchannelAuthenticationAuthenticationFailed means the end user // could not be authenticated at all. BackchannelAuthenticationAuthenticationFailed )
type BackchannelAuthenticationStore ¶ added in v0.18.0
type BackchannelAuthenticationStore interface {
CreateBackchannelAuthentication(ctx context.Context, record NewBackchannelAuthentication) error
// DecideBackchannelAuthentication atomically records the terminal
// outcome of a pending request identified by decision.HandleHash,
// returning DecidedBackchannelAuthentication (see its own doc
// comment). A second call for the same HandleHash must fail —
// exactly one decision may ever be recorded, the same way
// CompleteAuthorization's interaction handle is single-use.
DecideBackchannelAuthentication(ctx context.Context, decision DecideBackchannelAuthentication) (DecidedBackchannelAuthentication, error)
// PollBackchannelAuthentication atomically:
// - returns *BackchannelAuthenticationExpiredError once ExpiresAt
// has passed, regardless of decision status;
// - returns *BackchannelAuthenticationSlowDownError if called again
// before PollInterval has elapsed since the previous poll for
// this AuthReqIDHash (the interval is tracked internally — the
// caller supplies no interval of its own, only Now, so the
// check-and-record-last-poll-time step stays atomic rather than
// a check-then-act race);
// - returns Status Pending, unconsumed, on every poll before a
// decision has been recorded;
// - returns Status Denied or AuthenticationFailed, unconsumed and
// freely repeatable, once DecideBackchannelAuthentication has
// recorded one — mirroring RedeemRefreshToken's reusable
// contract, not RedeemAuthorizationCode's single-use one;
// - on the first poll to observe Status Approved, atomically marks
// the record redeemed and returns it; every subsequent poll for
// the same AuthReqIDHash returns
// *BackchannelAuthenticationAlreadyRedeemedError — an approved
// auth_req_id issues tokens exactly once (CIBA §10.3), the same
// single-use guarantee RedeemAuthorizationCode has.
// It returns a plain error if AuthReqIDHash is unknown.
PollBackchannelAuthentication(ctx context.Context, poll PollBackchannelAuthentication) (PolledBackchannelAuthentication, error)
}
BackchannelAuthenticationStore persists CIBA backchannel authentication requests: creation, the out-of-band decision, and client polling for that decision.
type BackchannelTokenDeliveryMode ¶ added in v0.18.0
type BackchannelTokenDeliveryMode uint8
BackchannelTokenDeliveryMode is the closed set of mechanisms this server uses to tell a registered client that a CIBA backchannel authentication request has reached a decision (CIBA Core 1.0 §7–§10). FAPI-CIBA permits only poll and ping — push is not implemented.
const ( // BackchannelTokenDeliveryModePoll means the client itself polls // the token endpoint on a schedule (CIBA §10.3) — the zero value, // so every registered client that predates this field keeps // behaving exactly as it did before. BackchannelTokenDeliveryModePoll BackchannelTokenDeliveryMode = iota // BackchannelTokenDeliveryModePing means this server proactively // notifies the client's own BackchannelClientNotificationEndpoint // once a decision is reached (CIBA §10.2), so the client can poll // immediately instead of on a fixed schedule. The client's backup // polling (CIBA §10.3) remains valid regardless — a missed or // failed notification is never itself an error condition. BackchannelTokenDeliveryModePing )
type BeginAuthorizationTransaction ¶
type BeginAuthorizationTransaction struct {
// Reference is the request_uri's reference component (see
// internal/par.SplitRequestURI).
Reference string
// Handle is the newly generated interaction handle to associate with
// this pushed authorization request's data, for retrieval when the
// interaction later completes.
Handle string
// HandleExpiresAt bounds how long Handle remains valid.
HandleExpiresAt time.Time
}
BeginAuthorizationTransaction is the input to TransactionStore.BeginAuthorization.
type Capabilities ¶
type Capabilities struct {
// Durable means state survives a process restart — an in-memory map
// is not durable.
Durable bool
// AtomicConsume means every Consume/Redeem/BeginAuthorization/
// CompleteAuthorization-style method is a single atomic
// check-and-retire operation: two concurrent calls for the same key
// can never both succeed.
AtomicConsume bool
// SerializableRedemption means concurrent operations on *different*
// keys do not observe each other's partial effects — the backend
// provides at least SERIALIZABLE (or equivalent) isolation for the
// operations this package's interfaces define.
SerializableRedemption bool
// CrossInstanceConsistent means the store is safe to share across
// multiple server processes/instances (e.g. a shared database, not a
// per-process in-memory map) — required for any horizontally scaled
// deployment.
CrossInstanceConsistent bool
// EncryptedAtRest means persisted data is encrypted at rest.
EncryptedAtRest bool
}
Capabilities describes what a storage backend's implementer asserts about its operational guarantees.
type ClientAuthMethod ¶ added in v0.18.0
type ClientAuthMethod uint8
ClientAuthMethod is the closed set of mechanisms a registered client authenticates itself to this server with.
const ( // ClientAuthMethodPrivateKeyJWT authenticates via a signed client // assertion (RFC 7523) — the zero value, so every registered client // that predates this field keeps behaving exactly as it did before. ClientAuthMethodPrivateKeyJWT ClientAuthMethod = iota // ClientAuthMethodSelfSignedTLSClientAuth authenticates by exact // match of the presented TLS client certificate's RFC 8705 §3.1 // x5t#S256 thumbprint against ExpectedCertificateThumbprint (RFC // 8705 §2.2) — no CA trust required; the certificate need only be // the one previously registered out of band. ClientAuthMethodSelfSignedTLSClientAuth // ClientAuthMethodTLSClientAuth authenticates by exact string match // of the presented certificate's subject DN against ExpectedSubjectDN // — RFC 8705 §2.1's "tls_client_auth_subject_dn", the one subject- // matching rule this package implements of the four §2.1 defines // (san_dns/san_uri/san_ip/san_email are not implemented). This // package does not itself validate the certificate against a CA // trust store — that's a deployment/adapter concern // (tls.Config.ClientCAs), the same posture SenderConstrainMTLS // already documents for sender-constraining. ClientAuthMethodTLSClientAuth )
type ClientRepository ¶
type ClientRepository interface {
ResolveClient(ctx context.Context, id fapi.ClientID) (RegisteredClient, error)
}
ClientRepository resolves a registered client by ID.
type CompleteAuthorizationTransaction ¶
type CompleteAuthorizationTransaction struct {
// Handle is the interaction handle BeginAuthorization returned.
Handle string
}
CompleteAuthorizationTransaction is the input to TransactionStore.CompleteAuthorization.
type CompletedInteraction ¶
type CompletedInteraction struct {
ClientID fapi.ClientID
Parameters map[string]json.RawMessage
TokenClaims map[string]json.RawMessage
ExpiresAt time.Time
}
CompletedInteraction is what CompleteAuthorization retrieves and consumes for one in-progress interaction.
type ConsumedSession ¶
type ConsumedSession struct {
Nonce string
PKCEVerifier string
ExpectedIssuer string
ExpectedRedirectURI string
ExpectedResponseMode string
ExpiresAt time.Time
}
ConsumedSession is what Consume returns for a successfully consumed session — the correlation state Create persisted, for the caller to compare against what the authorization response actually carried.
type DecideBackchannelAuthentication ¶ added in v0.18.0
type DecideBackchannelAuthentication struct {
HandleHash [32]byte
// Status must be one of Approved, Denied or AuthenticationFailed —
// never Pending.
Status BackchannelAuthenticationStatus
// Subject, Scope, AuthTime, ACR and AMR are set only when Status is
// Approved.
Subject string
Scope []string
AuthTime time.Time
ACR string
AMR []string
// Reason is an optional, human-readable explanation, set when Status
// is Denied or AuthenticationFailed — mirrors Deny/AuthenticationFailed's
// own reason parameter.
Reason string
}
DecideBackchannelAuthentication is the input to BackchannelAuthenticationStore.DecideBackchannelAuthentication.
type DecidedBackchannelAuthentication ¶ added in v0.18.0
type DecidedBackchannelAuthentication struct {
ClientID fapi.ClientID
// DeliveryMode is "poll" or "ping", mirroring
// NewBackchannelAuthentication.DeliveryMode exactly.
DeliveryMode string
// ClientNotificationToken is "" for a "poll" DeliveryMode; for
// "ping", it's the bearer token the caller must present to the
// client's own notification endpoint.
ClientNotificationToken fapi.Secret
// AuthReqID mirrors NewBackchannelAuthentication.AuthReqID — "" for
// a "poll" DeliveryMode; for "ping", the literal auth_req_id value
// CIBA §10.2 requires the notification body to carry.
AuthReqID string
}
DecidedBackchannelAuthentication is returned by a successful DecideBackchannelAuthentication: ClientID for the caller's own audit logging (mirroring CompleteAuthorization's CompletedInteraction.ClientID), plus DeliveryMode and ClientNotificationToken — copied straight from the record NewBackchannelAuthentication originally created — so the caller can dispatch a CIBA §10.2 ping notification without a second round trip to this store.
type GrantStore ¶
type GrantStore interface {
CreateAuthorizationCode(ctx context.Context, code NewAuthorizationCode) error
// RedeemAuthorizationCode atomically retrieves and consumes the
// authorization code identified by CodeHash — a second call with the
// same CodeHash must fail. It returns an error if CodeHash is
// unknown or already consumed; the caller checks the returned
// record's own expiry (ExpiresAt) itself, the same way
// BeginAuthorization and CompleteAuthorization do. On a repeat call
// specifically (as opposed to an unknown code), the returned error
// must satisfy errors.As into *AuthorizationCodeAlreadyRedeemedError
// — see its own doc comment.
RedeemAuthorizationCode(ctx context.Context, redemption AuthorizationCodeRedemption) (RedeemedAuthorizationCode, error)
// RecordIssuedAccessToken associates the access token's
// revocation-lookup key (see AuthorizationCodeAlreadyRedeemedError.
// IssuedAccessTokenKey) issued when codeHash was (successfully)
// redeemed, purely so a later reuse of the same code can report
// which token was issued the first time (RFC 6749 §4.1.2). Called
// once, right after a successful redemption issues its access
// token. A no-op implementation (return nil, remember nothing) is
// entirely valid for a deployment that doesn't support revocation
// — costs nothing to implement, and RedeemAuthorizationCode's
// reuse error just always carries an empty IssuedAccessTokenKey in
// that case.
RecordIssuedAccessToken(ctx context.Context, codeHash [32]byte, key string, expiresAt time.Time) error
// RecordIssuedRefreshToken is RecordIssuedAccessToken's counterpart
// for the refresh token issued alongside it, when one is (the
// authorization included "offline_access"). Same no-op-able
// contract.
RecordIssuedRefreshToken(ctx context.Context, codeHash [32]byte, refreshTokenHash [32]byte, expiresAt time.Time) error
CreateRefreshToken(ctx context.Context, token NewRefreshToken) error
// RedeemRefreshToken retrieves the refresh token identified by
// TokenHash. Unlike RedeemAuthorizationCode, this is deliberately
// NOT single-use: FAPI2-SP-FINAL requirement 5.3.2.1-9 states an
// authorization server "shall not use refresh token rotation except
// in extraordinary circumstances", so RefreshAccessToken never
// consumes or replaces the presented token — it stays valid for
// repeated use until it expires (or is otherwise revoked). It
// returns an error only if TokenHash is unknown or has been
// revoked (see RevokeRefreshToken — RFC 6749 doesn't need a
// distinct error code for "revoked" vs "invalid"); the caller
// checks the returned record's own expiry (ExpiresAt) itself, the
// same way BeginAuthorization and CompleteAuthorization do for
// their own redemptions.
RedeemRefreshToken(ctx context.Context, redemption RefreshTokenRedemption) (RedeemedRefreshToken, error)
// RevokeRefreshToken marks a previously-created refresh token (by
// the same hash CreateRefreshToken stored it under) as no longer
// redeemable — used when its originating authorization code is
// detected being reused (RFC 6749 §4.1.2's "all tokens"). A
// subsequent RedeemRefreshToken for tokenHash must fail. A no-op
// implementation is valid for a deployment that doesn't support
// revocation, the same as RecordIssuedAccessToken.
RevokeRefreshToken(ctx context.Context, tokenHash [32]byte) error
}
GrantStore persists issued authorization codes and refresh tokens.
type LookedUpAccessToken ¶
type LookedUpAccessToken struct {
ClientID fapi.ClientID
Subject string
Scope []string
Thumbprint string
SenderConstrain SenderConstrain
Claims map[string]json.RawMessage
ExpiresAt time.Time
}
LookedUpAccessToken is what LookupAccessToken returns for a known token.
type NewAccessToken ¶
type NewAccessToken struct {
TokenHash [32]byte
ClientID fapi.ClientID
Subject string
Scope []string
Thumbprint string
// SenderConstrain records which mechanism Thumbprint represents (a
// DPoP proof key thumbprint or an mTLS client certificate
// thumbprint) — an opaque token has no self-describing wire format
// the way a JWT's "cnf.jkt"/"cnf.x5t#S256" claim name does, so this
// is how resource.OpaqueAccessTokens.ResolveAccessToken tells
// Verify() which credential to expect. Zero value
// (SenderConstrainDPoP) matches every access token issued before
// this field existed.
SenderConstrain SenderConstrain
Claims map[string]json.RawMessage
ExpiresAt time.Time
}
NewAccessToken is what CreateAccessToken persists for one issued opaque access token. TokenHash is the SHA-256 digest of the raw token value, never the value itself — the same digest-only discipline as NewAuthorizationCode.CodeHash/NewRefreshToken.TokenHash. Only relevant to an opaque AccessTokenIssuer/AccessTokenResolver (see server.OpaqueAccessTokens/resource.OpaqueAccessTokens) — a deployment issuing JWT access tokens never calls this.
type NewAuthorizationCode ¶
type NewAuthorizationCode struct {
CodeHash [32]byte
ClientID fapi.ClientID
RedirectURI string
CodeChallenge string
CodeChallengeMethod string
// DPoPJKT is the "dpop_jkt" authorization request parameter (RFC
// 9449 §10), if the client sent one — "" if it didn't.
// ExchangeAuthorizationCode must reject a token request whose DPoP
// proof key thumbprint doesn't match a non-empty value here.
DPoPJKT string
Subject string
Scope []string
Nonce string // "" if the authorization request carried none
AuthTime time.Time
ACR string
AMR []string
// TokenClaims are the validated extension parameter values
// (extension.Definition.ReturnInTokenClaims) carried by the
// authorization request this code grants — see
// storage.NewPARRecord.TokenClaims. RedeemAuthorizationCode must
// return them unmodified, so ExchangeAuthorizationCode can copy them
// into the access and ID tokens it issues.
TokenClaims map[string]json.RawMessage
// RequestedIDTokenClaims and RequestedUserinfoClaims are the claim
// names the authorization request's "claims" parameter (OIDC Core
// §5.5) asked for, split by delivery location — nil if the request
// carried no "claims" parameter, or asked for nothing at that
// location. ExchangeAuthorizationCode must never embed an identity
// claim outside of what's named here: an IdentityClaimsSource
// deployment may hold more claims than were actually requested, and
// returning them anyway is a data-minimization violation, not a
// convenience.
RequestedIDTokenClaims []string
RequestedUserinfoClaims []string
ExpiresAt time.Time
}
NewAuthorizationCode is what CreateAuthorizationCode persists for one issued authorization code. CodeHash is the SHA-256 digest of the raw code value — matching ReplayStore's digest-only philosophy — never the code itself; the raw value exists only long enough to be hashed here and returned to the client in the redirect response.
type NewBackchannelAuthentication ¶ added in v0.18.0
type NewBackchannelAuthentication struct {
// AuthReqIDHash is the SHA-256 digest of the raw auth_req_id value
// handed to the client — matching NewAuthorizationCode.CodeHash's
// digest-only discipline, used to look this record up by the value a
// client presents back. Required unconditionally, regardless of
// DeliveryMode.
AuthReqIDHash [32]byte
// AuthReqID is the same auth_req_id value in the clear. Unlike
// AuthReqIDHash, this is required only for DeliveryMode "ping": CIBA
// §10.2 requires the ping notification body itself carry the literal
// auth_req_id, so the server must be able to produce it again later
// — the same reasoning ClientNotificationToken's own doc comment
// gives for why that field can't be digest-only either. Leave "" for
// DeliveryMode "poll", where nothing ever needs it back.
AuthReqID string
// HandleHash is the SHA-256 digest of the raw, embedder-facing
// handle value — a distinct identifier from AuthReqIDHash, never
// handed to the OAuth client, the same separation
// InteractionHandle keeps from the PAR reference.
HandleHash [32]byte
ClientID fapi.ClientID
// Parameters are the backchannel authentication request's own
// parameters (scope, login_hint/login_hint_token/id_token_hint,
// acr_values, binding_message) — mirrors NewPARRecord.Parameters.
Parameters map[string]json.RawMessage
// TokenClaims mirrors NewPARRecord.TokenClaims.
TokenClaims map[string]json.RawMessage
// RequestedIDTokenClaims and RequestedUserinfoClaims mirror
// NewAuthorizationCode's fields of the same name — the claim names
// this request's own "claims" parameter (OIDC Core §5.5) asked for,
// split by delivery location.
RequestedIDTokenClaims []string
RequestedUserinfoClaims []string
// DeliveryMode is "poll" or "ping" (CIBA §7.1's "backchannel_token_delivery_mode",
// restricted to these two values — FAPI-CIBA prohibits "push").
DeliveryMode string
// ClientNotificationToken is the raw bearer value this server must
// later present to the client's own notification endpoint in ping
// mode — "" for poll mode. Unlike every other secret this package
// stores, this cannot be digest-only: the server is the party that
// later presents it, not merely compares against it.
ClientNotificationToken fapi.Secret
// DPoPJKT is the "dpop_jkt" request parameter, if the client sent
// one at BC-Auth time — "" if it didn't (see NewAuthorizationCode.DPoPJKT
// for the equivalent PAR-side field).
DPoPJKT string
// PollInterval is the minimum time that must elapse between two
// polls of this same request before PollBackchannelAuthentication
// returns *BackchannelAuthenticationSlowDownError.
PollInterval time.Duration
ExpiresAt time.Time
}
NewBackchannelAuthentication is what CreateBackchannelAuthentication persists for one client-initiated backchannel authentication request.
type NewPARRecord ¶
type NewPARRecord struct {
// Reference is the request_uri's reference component (see
// internal/par.SplitRequestURI) — the lookup key, not the full
// request_uri string.
Reference string
ClientID fapi.ClientID
Parameters map[string]json.RawMessage
// TokenClaims are the already-validated extension parameter values
// (extension.Definition.ReturnInTokenClaims) this pushed request
// carried, keyed by wire name — the subset of Parameters that should
// be copied into any access/ID token this authorization eventually
// produces. A TransactionStore implementation must carry this field
// through to PushedAuthorizationRequest and CompletedInteraction
// unmodified, exactly as it already does for Parameters.
TokenClaims map[string]json.RawMessage
ExpiresAt time.Time
}
NewPARRecord is what CreatePAR persists for one pushed authorization request.
type NewRefreshToken ¶
type NewRefreshToken struct {
TokenHash [32]byte
ClientID fapi.ClientID
Subject string
Scope []string
Thumbprint string
AuthTime time.Time
ACR string
AMR []string
TokenClaims map[string]json.RawMessage
// RequestedIDTokenClaims and RequestedUserinfoClaims carry forward
// the original authorization request's "claims" parameter (see
// NewAuthorizationCode's fields of the same name) so a refreshed ID
// token, and a refreshed access token's embedded
// RequestedUserinfoClaimsKey, keep respecting it across rotations —
// not just the first token issued.
RequestedIDTokenClaims []string
RequestedUserinfoClaims []string
ExpiresAt time.Time
}
NewRefreshToken is what CreateRefreshToken persists for one issued refresh token. TokenHash is the SHA-256 digest of the raw token value, never the value itself — the same digest-only discipline as NewAuthorizationCode.CodeHash. Thumbprint is the DPoP key thumbprint presented when this token was issued — recorded for reference, but RefreshAccessToken does not require a later refresh request to present the same key: every client this server accepts is confidential (client_assertion is always required), and RFC 9449 §5 does not bind a confidential client's refresh token to a specific DPoP key.
type NewSession ¶
type NewSession struct {
State string
// Nonce is the value the client generated for the authorization
// request's "nonce" parameter, to check against an issued ID token's
// own nonce claim.
Nonce string
// PKCEVerifier is the code_verifier the client generated; ExchangeCode
// presents it to redeem the authorization code.
PKCEVerifier string
// ExpectedIssuer is the authorization server this session's
// authorization response must come from.
ExpectedIssuer string
// ExpectedRedirectURI is the redirect_uri this session's authorization
// request carried, presented again at code exchange.
ExpectedRedirectURI string
// ExpectedResponseMode is the response mode this session's
// authorization request declared (plain query parameters, or a signed
// JARM response) — checked against how the response actually arrived,
// so a downgrade from a signed response to a plain one can't be used
// to bypass JARM's integrity guarantee.
ExpectedResponseMode string
ExpiresAt time.Time
}
NewSession is what Create persists for one in-progress client authorization attempt, keyed by State — the value the client generated for the authorization request's "state" parameter.
type NonceConsumption ¶ added in v0.16.0
type NonceConsumption struct {
// Nonce is the value a presented DPoP proof's own "nonce" claim
// carried — the lookup key.
Nonce string
}
NonceConsumption is the input to NonceStore.Consume.
type NonceIssuance ¶ added in v0.16.0
NonceIssuance is what Issue persists for one DPoP nonce a verifier has handed out — either as an RFC 9449 §8/§9 challenge, or proactively alongside a successful response — keyed by Nonce itself.
type NonceRecord ¶ added in v0.16.0
NonceRecord is what Consume returns for a successfully consumed nonce — the expiry Issue persisted, for the caller to compare against the time it's verifying at, the same division of responsibility every other store in this package uses (the store itself never judges expiry).
type NonceStore ¶ added in v0.16.0
type NonceStore interface {
Issue(ctx context.Context, issuance NonceIssuance) error
// Consume atomically retrieves and retires the nonce identified by
// consumption.Nonce — a second call with the same value must fail,
// exactly like SessionStore.Consume, so a captured nonce can never
// be presented twice. It returns an error if the nonce is unknown or
// already consumed; the caller checks the returned record's own
// expiry (ExpiresAt) against the time it's verifying at.
Consume(ctx context.Context, consumption NonceConsumption) (NonceRecord, error)
}
NonceStore persists DPoP nonces a verifier has issued, keyed by the nonce value itself. Like SessionStore, it exposes no generic CRUD — Consume is the only way to check a nonce, and it always retires the record it returns.
type PollBackchannelAuthentication ¶ added in v0.18.0
PollBackchannelAuthentication is the input to BackchannelAuthenticationStore.PollBackchannelAuthentication.
type PolledBackchannelAuthentication ¶ added in v0.18.0
type PolledBackchannelAuthentication struct {
Status BackchannelAuthenticationStatus
ClientID fapi.ClientID
Subject string
Scope []string
AuthTime time.Time
ACR string
AMR []string
TokenClaims map[string]json.RawMessage
DPoPJKT string
Reason string
// RequestedIDTokenClaims and RequestedUserinfoClaims mirror
// NewBackchannelAuthentication's fields of the same name.
RequestedIDTokenClaims []string
RequestedUserinfoClaims []string
}
PolledBackchannelAuthentication is what a successful PollBackchannelAuthentication returns.
type PushedAuthorizationRequest ¶
type PushedAuthorizationRequest struct {
ClientID fapi.ClientID
Parameters map[string]json.RawMessage
TokenClaims map[string]json.RawMessage
ExpiresAt time.Time
}
PushedAuthorizationRequest is what BeginAuthorization retrieves for one previously pushed authorization request. Retrieving it does not by itself consume the request — see BeginAuthorization's doc comment.
type RedeemedAuthorizationCode ¶
type RedeemedAuthorizationCode struct {
ClientID fapi.ClientID
RedirectURI string
CodeChallenge string
CodeChallengeMethod string
DPoPJKT string
Subject string
Scope []string
Nonce string
AuthTime time.Time
ACR string
AMR []string
TokenClaims map[string]json.RawMessage
// RequestedIDTokenClaims and RequestedUserinfoClaims mirror
// NewAuthorizationCode's fields of the same name.
RequestedIDTokenClaims []string
RequestedUserinfoClaims []string
ExpiresAt time.Time
}
RedeemedAuthorizationCode is what RedeemAuthorizationCode returns for a successfully redeemed code.
type RedeemedRefreshToken ¶
type RedeemedRefreshToken struct {
ClientID fapi.ClientID
Subject string
Scope []string
Thumbprint string
AuthTime time.Time
ACR string
AMR []string
TokenClaims map[string]json.RawMessage
// RequestedIDTokenClaims and RequestedUserinfoClaims mirror
// NewRefreshToken's fields of the same name.
RequestedIDTokenClaims []string
RequestedUserinfoClaims []string
ExpiresAt time.Time
}
RedeemedRefreshToken is what RedeemRefreshToken returns for a successfully redeemed token.
type RefreshTokenRedemption ¶
type RefreshTokenRedemption struct {
// TokenHash is the SHA-256 digest of the presented refresh token
// value — the same digest CreateRefreshToken stored it under.
TokenHash [32]byte
}
RefreshTokenRedemption is the input to GrantStore.RedeemRefreshToken.
type RegisteredClient ¶
type RegisteredClient struct {
// contains filtered or unexported fields
}
RegisteredClient is the exact, validated configuration of one registered OAuth client. It is immutable and can only be constructed through NewRegisteredClient — a caller cannot return arbitrary discovery or registration JSON in its place.
func NewRegisteredClient ¶
func NewRegisteredClient(cfg RegisteredClientConfig) (RegisteredClient, error)
NewRegisteredClient validates cfg and returns an immutable RegisteredClient.
func (RegisteredClient) AllowsScope ¶
func (c RegisteredClient) AllowsScope(scope string) bool
AllowsScope reports whether scope is in this client's registered set of allowed scopes.
func (RegisteredClient) BackchannelAuthenticationRequestAlgorithm ¶ added in v0.18.0
func (c RegisteredClient) BackchannelAuthenticationRequestAlgorithm() (algorithm fapi.SignatureAlgorithm, permitted bool)
BackchannelAuthenticationRequestAlgorithm returns the algorithm this client's signed CIBA backchannel authentication requests must be signed with, and whether the client is permitted to use CIBA at all.
func (RegisteredClient) BackchannelClientNotificationEndpoint ¶ added in v0.18.0
func (c RegisteredClient) BackchannelClientNotificationEndpoint() fapi.URL
BackchannelClientNotificationEndpoint returns where this server POSTs a bearer-authenticated notification once a CIBA decision is reached, under BackchannelTokenDeliveryModePing.
func (RegisteredClient) BackchannelTokenDeliveryMode ¶ added in v0.18.0
func (c RegisteredClient) BackchannelTokenDeliveryMode() BackchannelTokenDeliveryMode
BackchannelTokenDeliveryMode returns how this server tells this client a CIBA decision was reached.
func (RegisteredClient) ClientAssertionAlgorithm ¶
func (c RegisteredClient) ClientAssertionAlgorithm() fapi.SignatureAlgorithm
ClientAssertionAlgorithm returns the algorithm this client's client assertions must be signed with.
func (RegisteredClient) ClientAuthMethod ¶ added in v0.18.0
func (c RegisteredClient) ClientAuthMethod() ClientAuthMethod
ClientAuthMethod returns how this client authenticates itself.
func (RegisteredClient) ExpectedCertificateThumbprint ¶ added in v0.18.0
func (c RegisteredClient) ExpectedCertificateThumbprint() string
ExpectedCertificateThumbprint returns the RFC 8705 §3.1 x5t#S256 value this client's TLS certificate must match under ClientAuthMethodSelfSignedTLSClientAuth.
func (RegisteredClient) ExpectedSubjectDN ¶ added in v0.18.0
func (c RegisteredClient) ExpectedSubjectDN() string
ExpectedSubjectDN returns the subject DN this client's TLS certificate must match under ClientAuthMethodTLSClientAuth.
func (RegisteredClient) HasRedirectURI ¶
func (c RegisteredClient) HasRedirectURI(candidate string) bool
HasRedirectURI reports whether candidate is exactly one of this client's registered redirect URIs (RegisteredRedirectURI.Equal semantics — exact match, no normalization).
func (RegisteredClient) ID ¶
func (c RegisteredClient) ID() fapi.ClientID
ID returns the client's ID.
func (RegisteredClient) IDTokenEncryption ¶ added in v0.4.0
func (c RegisteredClient) IDTokenEncryption() (keyManagement fapi.KeyManagementAlgorithm, contentEncryption fapi.ContentEncryptionAlgorithm, enabled bool)
IDTokenEncryption returns the algorithms this client's ID tokens must be encrypted with, and whether the client registered for encrypted ID tokens at all.
func (RegisteredClient) RequestObjectAlgorithm ¶
func (c RegisteredClient) RequestObjectAlgorithm() (algorithm fapi.SignatureAlgorithm, permitted bool)
RequestObjectAlgorithm returns the algorithm this client's request objects must be signed with, and whether the client is permitted to submit request objects at all.
func (RegisteredClient) SenderConstrain ¶ added in v0.18.0
func (c RegisteredClient) SenderConstrain() SenderConstrain
SenderConstrain returns how this client's tokens are sender-constrained.
func (RegisteredClient) UserInfoEncryption ¶ added in v0.17.0
func (c RegisteredClient) UserInfoEncryption() (keyManagement fapi.KeyManagementAlgorithm, contentEncryption fapi.ContentEncryptionAlgorithm, enabled bool)
UserInfoEncryption returns the algorithms this client's UserInfo responses must be encrypted with, and whether the client registered for encrypted UserInfo responses at all.
type RegisteredClientConfig ¶
type RegisteredClientConfig struct {
ID fapi.ClientID
RedirectURIs []fapi.RegisteredRedirectURI
// ClientAuthMethod selects how this client authenticates —
// ClientAuthMethodPrivateKeyJWT (the zero value/default),
// ClientAuthMethodSelfSignedTLSClientAuth, or
// ClientAuthMethodTLSClientAuth.
ClientAuthMethod ClientAuthMethod
// ClientAssertionAlgorithm is the only algorithm this client's
// private_key_jwt client assertions are accepted under. It is never
// inferred from an assertion's own header. Required only when
// ClientAuthMethod is ClientAuthMethodPrivateKeyJWT.
ClientAssertionAlgorithm fapi.SignatureAlgorithm
// ExpectedCertificateThumbprint is the RFC 8705 §3.1 x5t#S256 value
// (base64url, no padding — the same shape internal/mtls.Thumbprint
// produces) this client's TLS certificate must match. Required only
// when ClientAuthMethod is ClientAuthMethodSelfSignedTLSClientAuth.
ExpectedCertificateThumbprint string
// ExpectedSubjectDN is the exact string this client's certificate's
// subject must match, via Go's own pkix.Name.String() serialization
// (crypto/x509.Certificate.Subject.String()) — not full RFC 4514
// canonicalization, so this comparison is case-sensitive and
// attribute-order-sensitive; register the DN exactly as Go
// serializes the client's actual certificate. Required only when
// ClientAuthMethod is ClientAuthMethodTLSClientAuth.
ExpectedSubjectDN string
// RequestObjectAlgorithm is the only algorithm this client's signed
// request objects are accepted under. Leave zero if the client is
// not permitted to submit request objects at all.
RequestObjectAlgorithm fapi.SignatureAlgorithm
// SenderConstrain selects how this client's tokens are
// sender-constrained — SenderConstrainDPoP (the zero value) or
// SenderConstrainMTLS. Every existing client config that never sets
// this field keeps using DPoP, unchanged.
SenderConstrain SenderConstrain
// IDTokenEncryptionKeyManagement/IDTokenEncryptionContentEncryption,
// if set (together — both zero, or both set), mean every ID token
// issued to this client is encrypted (OIDC Core §2) using these
// algorithms — the local record of this client's own
// id_token_encrypted_response_alg/enc registration. Leave both zero
// if the client did not register for encrypted ID tokens. Checked
// against server.Config.Algorithms' own allow-list at issuance time,
// not here: this type validates internal consistency only, not
// server-wide policy, the same way ClientAssertionAlgorithm/
// RequestObjectAlgorithm are validated for shape here and checked
// against server-wide policy elsewhere.
IDTokenEncryptionKeyManagement fapi.KeyManagementAlgorithm
IDTokenEncryptionContentEncryption fapi.ContentEncryptionAlgorithm
// UserInfoEncryptionKeyManagement/UserInfoEncryptionContentEncryption
// mirror IDTokenEncryptionKeyManagement/ContentEncryption exactly,
// but for this client's own, independent
// userinfo_encrypted_response_alg/enc registration (OIDC Core
// §5.3.2) — a client may register for one without the other. Leave
// both zero if the client did not register for encrypted UserInfo
// responses.
UserInfoEncryptionKeyManagement fapi.KeyManagementAlgorithm
UserInfoEncryptionContentEncryption fapi.ContentEncryptionAlgorithm
// BackchannelAuthenticationRequestAlgorithm is the only algorithm
// this client's signed CIBA backchannel authentication requests are
// accepted under. Leave zero if the client is not permitted to use
// CIBA at all — since FAPI-CIBA always requires a signed request
// (unlike RequestObjectAlgorithm, whose signing is profile-dependent
// for PAR), this single field doubles as this client's CIBA opt-in
// flag.
BackchannelAuthenticationRequestAlgorithm fapi.SignatureAlgorithm
// BackchannelTokenDeliveryMode selects how this server tells this
// client a CIBA decision was reached — BackchannelTokenDeliveryModePoll
// (the zero value/default) or BackchannelTokenDeliveryModePing.
// Meaningless (and must stay the zero value) for a client not
// permitted to use CIBA at all — see
// BackchannelAuthenticationRequestAlgorithm's own doc comment.
BackchannelTokenDeliveryMode BackchannelTokenDeliveryMode
// BackchannelClientNotificationEndpoint is where this server POSTs
// a bearer-authenticated notification once a CIBA decision is
// reached (CIBA §10.2). Required exactly when
// BackchannelTokenDeliveryMode is BackchannelTokenDeliveryModePing;
// must be left unset for poll mode, since nothing would ever use it.
BackchannelClientNotificationEndpoint fapi.URL
AllowedScopes []string
}
RegisteredClientConfig is the input to NewRegisteredClient.
type ReplayNamespace ¶
type ReplayNamespace string
ReplayNamespace scopes a replayed-use digest to the subsystem that recorded it, so different roles and subsystems can never collide on the same use-once token even if they happen to hash to the same digest (e.g. "server:client-assertion", "server:request-object", "server:dpop", "resource:dpop").
type ReplayStore ¶
ReplayStore records a single-use digest, failing if it has already been recorded. Implementations must treat the check and the record as one atomic operation — two concurrent UseOnce calls for the same digest must never both succeed.
Only a digest is stored, never the value it was derived from — a complete client assertion, DPoP proof or request object must not be persisted just to detect its reuse.
type ReplayUse ¶
type ReplayUse struct {
Namespace ReplayNamespace
Digest [32]byte
ExpiresAt time.Time
}
ReplayUse is one use-once check: has Digest, scoped to Namespace, been seen before.
type SenderConstrain ¶ added in v0.18.0
type SenderConstrain uint8
SenderConstrain is the closed set of mechanisms a registered client's access (and refresh) tokens are sender-constrained with.
const ( // SenderConstrainDPoP binds a client's tokens to a DPoP proof key // (RFC 9449) — the zero value, so every registered client that // predates this field keeps behaving exactly as it did before. SenderConstrainDPoP SenderConstrain = iota // SenderConstrainMTLS binds a client's tokens to the TLS client // certificate presented on the connection that requested them // (RFC 8705 §3's "cnf.x5t#S256"), instead of a DPoP proof. Like // DPoP, this needs no client registration or CA trust store of its // own — sender-constraining only compares thumbprints, it never // authenticates the client by its certificate. SenderConstrainMTLS )
type SessionConsumption ¶
type SessionConsumption struct {
// State is the value the client generated for this session's "state"
// parameter — the lookup key.
State string
}
SessionConsumption is the input to SessionStore.Consume.
type SessionStore ¶
type SessionStore interface {
Create(ctx context.Context, session NewSession) error
// Consume atomically retrieves and retires the session identified by
// State — a second call with the same State must fail, exactly like
// GrantStore's Redeem methods — so a callback (or an attacker
// replaying one) can never be processed twice. It returns an error if
// State is unknown or already consumed; the caller checks the
// returned record's own expiry (ExpiresAt) and compares every field
// against what the authorization response actually carried, the same
// division of responsibility TransactionStore and GrantStore use.
Consume(ctx context.Context, consumption SessionConsumption) (ConsumedSession, error)
}
SessionStore persists client-side authorization-flow state, keyed by the "state" parameter the client generated for one authorization attempt. Like TransactionStore and GrantStore, it exposes no generic CRUD (no GetSession, no DeleteSession) — Consume is the only way to retrieve a session, and it always retires the record it returns.
type StoreAssurance ¶
type StoreAssurance interface {
Capabilities() Capabilities
}
StoreAssurance is a self-asserted declaration of a storage backend's operational guarantees. A storage implementation (ClientRepository, TransactionStore, GrantStore, ReplayStore, SessionStore) optionally implements it by exposing a Capabilities method; server checks it under AssuranceProduction rather than trusting that a store meant for a quick prototype (e.g. an in-memory map) is safe to run in production.
Because these properties are self-asserted, not verified, a downstream (or first-party) implementation should also run the reusable contract test suite this package provides — TestGrantStoreContract, TestTransactionStoreContract, TestReplayStoreContract and TestSessionStoreContract — against its own factory, rather than relying on the capability declaration alone. The contract suite verifies what's observable through the public interface (the single-use/atomic-consume guarantee under concurrency, faithful round-tripping of stored fields, replay/duplicate rejection, namespace isolation); it cannot verify claims that are specific to a backend's own storage technology and invisible at this interface (encryption at rest, cross-instance consistency, transactional rollback behavior) — verifying those remains the implementation's own responsibility.
type TransactionStore ¶
type TransactionStore interface {
CreatePAR(ctx context.Context, record NewPARRecord) error
// BeginAuthorization retrieves the pushed authorization request
// identified by Reference and associates it with a fresh Handle for
// retrieval when the interaction later completes. It may be called
// more than once for the same Reference — e.g. a client's browser
// revisiting the authorization endpoint before authenticating — and
// each such call mints its own independent Handle for the same
// underlying pushed request; this is what lets an authorization
// server satisfy FAPI 2.0 Security Profile 5.3.2.2 Note 3, which
// requires one-time use of request_uri be enforced at the point of
// authorization, not at the point of visiting the authorization
// endpoint. What must be single-use is completion, not the view:
// once any Handle minted from a given Reference is successfully
// consumed by CompleteAuthorization, that Reference itself is
// consumed, and every subsequent BeginAuthorization or
// CompleteAuthorization call for it — via any Handle, including
// ones already minted and still otherwise unexpired — must fail.
// It returns an error if Reference is unknown, already consumed by
// a completed interaction, or its own expiry
// (NewPARRecord.ExpiresAt) has passed.
BeginAuthorization(ctx context.Context, txn BeginAuthorizationTransaction) (PushedAuthorizationRequest, error)
// CompleteAuthorization atomically retrieves and consumes both the
// interaction identified by Handle and the underlying Reference it
// was minted from — a second call with the same Handle must fail,
// and so must any call for a different Handle minted from the same
// Reference, even one still otherwise valid and unexpired: exactly
// one Handle for a given Reference may ever complete, the same way
// an authorization code's redemption is single-use. It returns an
// error if Handle is unknown, its Reference has already been
// consumed by another completed interaction, or its own expiry
// (BeginAuthorizationTransaction.HandleExpiresAt) has passed.
CompleteAuthorization(ctx context.Context, txn CompleteAuthorizationTransaction) (CompletedInteraction, error)
}
TransactionStore persists server-side authorization-flow state.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package memstore provides in-memory implementations of every storage interface server.Dependencies needs (ClientRepository, TransactionStore, GrantStore, ReplayStore) — for local development and testing only.
|
Package memstore provides in-memory implementations of every storage interface server.Dependencies needs (ClientRepository, TransactionStore, GrantStore, ReplayStore) — for local development and testing only. |