storage

package
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 9 Imported by: 0

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

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

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

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

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

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

type NonceIssuance struct {
	Nonce     string
	ExpiresAt time.Time
}

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

type NonceRecord struct {
	ExpiresAt time.Time
}

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

type PollBackchannelAuthentication struct {
	AuthReqIDHash [32]byte
	Now           time.Time
}

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

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

type ReplayStore interface {
	UseOnce(ctx context.Context, use ReplayUse) error
}

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.

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.

Jump to

Keyboard shortcuts

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