From 44460a63b43da66bbd7126b2af1848f28a964035 Mon Sep 17 00:00:00 2001 From: Oscar Sanderson Date: Fri, 2 Oct 2026 09:59:25 +0800 Subject: [PATCH] docs: cover v0.42.0's features and add an examples overview Content fixes from a docs review against main: - GETTING_STARTED's login step passed time.Now() as the authentication time and didn't mention InteractionRequest.ACRValues/MaxAge. Copied as is, that silently defeats the max_age check v0.42.0 enforces. It now passes when the user actually authenticated, says when to re-authenticate, and shows GrantedAuthorization.GrantID. - Its resource-server step built VerifyRequest without PeerCertificate, so every mTLS-bound token would be refused. It now passes resource.PeerCertificateFromHTTP(r), and explains it. - It now covers RevokeGrant with Dependencies.Revocation, and serving your own grant at the token endpoint (TokenEndpointRequest.Parameters, AuthenticateAttestedClient, VerifyTokenRequestBinding, Config.AdditionalGrantTypes). - README's feature list gains refresh tokens and grant revocation, the OIDC claims parameter with acr_values and max_age, and embedder-served grants. - server/doc.go and client/doc.go list the new methods. A garbled sentence in client/doc.go is fixed. - ARCHITECTURE's package layout gains serverresource/, internal/grantrevocation/, cmd/ and examples/, and rules 3 and 7 gain the new flows. - UPGRADING's max_age section mentions the clock-skew leeway, the 100-year cap and passing the real authentication time. - The keys and keys/ephemeral package docs are corrected. examples/README.md is new. It has a table of the six demos with their stories and ports, a capability matrix mapping each FAPIgo feature to the demos that show it, and what every demo shares. The README links it. Each demo README's partial "runs alongside" port list now points at the overview's port table. Co-Authored-By: Claude Opus 5.5 --- ARCHITECTURE.md | 30 ++++++--- GETTING_STARTED.md | 59 +++++++++++++---- README.md | 7 +++ UPGRADING.md | 11 ++-- client/doc.go | 18 ++++-- examples/README.md | 91 +++++++++++++++++++++++++++ examples/decoupled-checkout/README.md | 4 +- examples/identity-check/README.md | 5 +- examples/linked-accounts/README.md | 5 +- examples/payment-consent/README.md | 7 +-- examples/payroll-run/README.md | 6 +- keys/doc.go | 6 +- keys/ephemeral/doc.go | 5 +- server/doc.go | 23 +++++-- 14 files changed, 223 insertions(+), 54 deletions(-) create mode 100644 examples/README.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 14b88f56..91f239e9 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -54,6 +54,7 @@ fapigo/ // package fapi: shared value types only ├── client/ // RP public API ├── server/ // AS public API ├── resource/ // RS verification API +├── serverresource/ // a resource.Verifier matching a server in the same process ├── federation/ // OpenID Federation 1.0 leaf-entity primitives — a shared │ // subsystem package like keys/storage, not a fourth role ├── extension/ // shared custom-parameter & RAR definitions @@ -82,13 +83,20 @@ fapigo/ // package fapi: shared value types only │ ├── httperror/ // shared error-type (server/resource/federation) mechanical bookkeeping │ ├── critical/ // JWS/JWE "crit" header parameter check (RFC 7515/7516) │ ├── authchallenge/ // WWW-Authenticate challenge parsing (RFC 9110 §11) +│ ├── grantrevocation/ // the grant_id claim and revocation key shared by server and resource │ ├── canonical/ // URL/JSON canonicalization │ ├── strictjson/ // case-sensitive JSON member names for JOSE/metadata decoding │ └── validation/ // generic strict-parsing helpers -└── conformance/ - ├── client/ // OIDF RP/client test plan config + scripts - ├── server/ // OIDF AS test plan config + scripts - └── resource/ // RS verification test vectors (not covered by OIDF) +├── cmd/ +│ ├── conformance-as/ // the AS (and its RS) the OIDF suite tests, built on server/resource +│ ├── conformance-client/ // the RP the OIDF suite tests, built on client +│ └── conformance-federation-trust-anchor/ // a Trust Anchor for the federation test plans +├── conformance/ +│ ├── client/ // OIDF RP/client test plan config + scripts +│ ├── server/ // OIDF AS test plan config + scripts +│ └── resource/ // RS verification test vectors (not covered by OIDF) +└── examples/ // runnable demos, each its own module, public API only (see examples/README.md) + └── internal/demokit/ // the demos' shared local TLS, CA and host routing ``` ## Design rules @@ -140,7 +148,10 @@ that operation. `BeginAuthorization` → `AuthorizationSession` (opaque, carries the authorization URL and a session handle) → `HandleAuthorizationResponse` → `ExchangeCode`, or the combined `CompleteAuthorization` so a caller -cannot skip callback validation before exchanging a code. The +cannot skip callback validation before exchanging a code; then +`RefreshTokens` for a new access token from the same grant. CIBA +(`BeginBackchannelAuthentication` → `PollBackchannelAuthentication`) +and `RequestClientCredentialsToken` are the other two flows. The intermediate `ValidatedAuthorizationResponse` is opaque and can only be constructed by `HandleAuthorizationResponse`. @@ -275,8 +286,13 @@ a PAR body or similar wire-level payload. ### 7. Server: a state machine over client-generated artefacts `PushAuthorizationRequest` → `BeginAuthorization` → `CompleteAuthorization` -→ `ExchangeAuthorizationCode` → `RefreshAccessToken`, plus `Metadata` and -`PublicJWKS`. The server only ever verifies; it must not expose +→ `ExchangeAuthorizationCode` → `RefreshAccessToken`, plus `RevokeGrant` +to withdraw a grant, the CIBA and client credentials grants, `Metadata` +and `PublicJWKS`. `AuthenticateAttestedClient` and +`VerifyTokenRequestBinding` let an embedder serve its own grant type at +the same token endpoint; they fit this rule because each is one +endpoint's own check, run exactly as the server's own grants run it, +not a generic primitive. The server only ever verifies; it must not expose `client`'s request-building functionality, and it must not expose a generic `HandleRequest(map[string]any)` or bare `ValidateJWT(token string)` — which claims, algorithms, audiences, replay checks and key diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 7d4f891b..be9b3482 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -75,7 +75,7 @@ cfg := server.Config{ Profile: server.ProfileFAPISecurity, // or ProfileFAPISecurityWithMessageSigning Algorithms: server.RecommendedAlgorithms(), Limits: server.RecommendedLimits(), - Assurance: server.AssuranceDevelopment, // AssuranceProduction once your real deps are ready + Assurance: server.AssuranceDevelopment, // AssuranceProduction once your real deps are ready } ``` @@ -143,9 +143,10 @@ deps := server.Dependencies{ AccessTokens: accessTokens, // Lets this server revoke a token it already issued when it later // detects the authorization code that produced it being reused - // (RFC 6749 §4.1.2). Pass server.NoRevocation{} instead to - // explicitly decline — see its doc comment for why declining must - // be a conscious choice, not a silent default. + // (RFC 6749 §4.1.2), and revoke a whole grant with RevokeGrant + // (step 5). Pass server.NoRevocation{} instead to explicitly + // decline — see its doc comment for why declining must be a + // conscious choice, not a silent default; RevokeGrant then refuses. Revocation: memstore.NewRevocationStore(), // Whether this package re-verifies an mTLS client certificate's // chain itself. NoClientCertificateChainTrust{} declines — right when @@ -195,9 +196,12 @@ switch a := action.(type) { case server.InteractionRequired: // Render whatever UI you want here — a password form, an SSO // redirect, WebAuthn, a magic link. a.Interaction carries the - // client ID, requested scope, and an unauthenticated login_hint - // to help pre-fill it. a.Handle must come back to - // CompleteAuthorization once the user is done. + // client ID, requested scope, an unauthenticated login_hint to + // help pre-fill it, and the client's authentication requirements: + // ACRValues (how strongly to authenticate) and, when HasMaxAge, + // MaxAge (how recent the authentication must be — re-authenticate + // a user whose existing session is older). a.Handle must come back + // to CompleteAuthorization once the user is done. case server.RedirectResponse: // no interaction needed — redirect the browser to a.Destination case server.LocalErrorResponse: @@ -211,7 +215,10 @@ verified their SSO assertion, whatever), conclude the interaction: ```go subjectID, _ := server.NewSubjectID(theRealAuthenticatedUserID) subject, _ := server.NewAuthenticatedSubject(subjectID) -authCtx, _ := server.NewAuthenticationContext(time.Now(), acr, amr) +// When the user actually authenticated — not time.Now() when you reuse +// an existing session: CompleteAuthorization answers login_required +// when this is older than the client's max_age. +authCtx, _ := server.NewAuthenticationContext(userAuthenticatedAt, acr, amr) result := server.Authorize(subject, authCtx, server.GrantedAuthorization{ Scope: whateverScopesTheUserActuallyApproved, @@ -223,6 +230,10 @@ result := server.Authorize(subject, authCtx, server.GrantedAuthorization{ // parameter (a.Interaction.RequestedClaims) that the user agreed to // release. Nil releases none. ApprovedIdentityClaims: whicheverRequestedClaimsTheUserApproved, + // Optional: your own ID for this grant, to withdraw it later with + // srv.RevokeGrant — for a "connected apps" page, say. It refuses + // the grant's refresh token and every access token issued from it. + GrantID: yourOwnIDForThisGrant, }) // or: server.Deny("user declined") / server.AuthenticationFailed("bad credentials") @@ -306,19 +317,32 @@ resolved there. `server.Server` has no built-in HTTP layer — every endpoint is a plain handler you write, calling the corresponding method (`PushAuthorizationRequest`, `ExchangeAuthorizationCode`, -`RefreshAccessToken`, `Metadata`, `PublicJWKS`). `cmd/conformance-as/router.go` +`RefreshAccessToken`, `Metadata`, `PublicJWKS`, and, when you enable +them, `RequestClientCredentialsToken` and the CIBA methods). `cmd/conformance-as/router.go` shows the complete routing table on a bare `net/http.ServeMux` — no framework dependency required, though nothing here stops you from using one. `cmd/conformance-as/token.go`, `par.go`, `metadata.go` and `jwks.go` are the corresponding handler implementations to read alongside `authorize.go`. +**Serving a grant this package doesn't.** To serve another grant type +at the same token endpoint — OpenID4VCI's `pre-authorized_code`, say — +read the request once with `server.TokenEndpointRequestFromHTTP` and +switch on `GrantType()`. For your own grant, take its form with +`Parameters()`, authenticate the client with `AuthenticateAttestedClient` +(`req.AttestedClientAuthentication()`), and check its DPoP proof or +client certificate with `VerifyTokenRequestBinding`: the same checks, +and the same replay records, as this package's own grants. List the +grant type in `Config.AdditionalGrantTypes` so `Metadata` advertises +it. Whether the client may use the grant, and the grant itself, stay +yours. + ## 7. Wire the resource server: verifying access tokens `resource.Verifier` is FAPI 2.0's third role, a deliberately separate package from `server` rather than a mode of it — verifying a presented access token is inseparable from the HTTP request it arrived on, so -`Verify(ctx, VerifyRequest{Method, URL, Authorization, DPoPProofs})` is +`Verify(ctx, VerifyRequest{Method, URL, Authorization, DPoPProofs, PeerCertificate})` is the only entry point, never a bare `VerifyJWT`. In a real deployment this is usually a wholly separate service protecting its own API; `cmd/conformance-as` only co-locates it in the same binary because the @@ -387,9 +411,9 @@ that AS's own metadata publishes them. `Dependencies.Revocation` needs the same care as the access-token format: if the AS revokes a token on detected authorization-code reuse -(RFC 6749 §4.1.2 — step 4's `Revocation` field), this resource server -must see that revocation too, or it will keep accepting a token the AS -has already disowned. Wire it to the *same* `RevocationSink`/ +(RFC 6749 §4.1.2 — step 4's `Revocation` field), or revokes a whole +grant with `RevokeGrant`, this resource server must see that revocation +too, or it will keep accepting a token the AS has already disowned. Wire it to the *same* `RevocationSink`/ `RevocationChecker` pair the AS uses — `memstore.NewRevocationStore()` already implements both, if co-located — or `resource.NoRevocation{}` to explicitly decline (matching `server.NoRevocation{}`'s own @@ -414,6 +438,8 @@ authCtx, err := verifier.Verify(ctx, resource.VerifyRequest{ URL: protectedResourceURL, // this endpoint's own fixed external URL — see below, never r.URL Authorization: r.Header.Get("Authorization"), DPoPProofs: r.Header.Values("DPoP"), // see below — never r.Header.Get("DPoP") + // The TLS client certificate, for an mTLS-bound access token. + PeerCertificate: resource.PeerCertificateFromHTTP(r), }) ``` @@ -445,6 +471,13 @@ which silently returns only the first of several duplicate headers. §7.1), so there's no adapter-side check to write here — unlike an older version of this library, which left that check to the caller. +`PeerCertificate` is the TLS client certificate the request arrived +with: an access token bound to a certificate (RFC 8705) is refused +without it. `resource.PeerCertificateFromHTTP(r)` reads it from `r.TLS`; +behind a proxy that terminates TLS, set it from however the proxy +forwards the certificate instead. Leave it out only if no client of +this API uses mTLS-bound tokens. + That's the whole surface: `resource.Verifier` has no other public entry point. Everything above `Verify` — routing, and what the protected API actually returns — is your own handler, same as step 5's login flow was diff --git a/README.md b/README.md index 3149ff95..b4177d77 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ variants and OAuth 2.0 attestation-based client authentication. - private_key_jwt client authentication - OAuth 2.0 Attestation-Based Client Authentication, including HAIP 1.0 x5c attester certificate chains - JAR / JARM · RAR (RFC 9396) · CIBA (poll & ping delivery) +- Refresh tokens (not rotated, per FAPI 2.0) and whole-grant revocation +- OpenID Connect: the `claims` parameter with per-claim consent, `acr_values` and enforced `max_age`, signed and encrypted ID tokens and UserInfo +- Grants you serve yourself at the token endpoint (OpenID4VCI's `pre-authorized_code`, say), with the server's own client authentication and DPoP/mTLS checks - OpenID Federation 1.0 (trust chains, automatic client registration, trust marks) - OpenID Certified™ for OP, RP and FAPI-CIBA OP conformance profiles — see below @@ -93,6 +96,10 @@ See [GETTING_STARTED.md](GETTING_STARTED.md) for a full walkthrough of standing up an authorization server and resource server end to end, including a runnable configuration you can start from. +Six runnable demos show these end to end, each with a guided tour and an +attack lab; [examples/README.md](examples/README.md) maps every capability +to the demo that shows it. + [examples/federated-union](examples/federated-union/README.md) is a runnable demo: three fictional countries' identity federations joined into one OpenID Federation, with cross-border sign-in, automatic diff --git a/UPGRADING.md b/UPGRADING.md index 13065521..4b7e4995 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -103,15 +103,18 @@ the user again. have passed since the user last actively authenticated, "the OP MUST attempt to actively re-authenticate the End-User". The server used to ignore `max_age`. It now validates it at the pushed authorization -request (a malformed value is `invalid_request`), surfaces it as +request (a value that isn't a whole number of seconds from 0 to 100 +years is `invalid_request`), surfaces it as `InteractionRequest.MaxAge`/`HasMaxAge`, and has `CompleteAuthorization` answer the client with `login_required` when the authentication time -passed to `NewAuthenticationContext` is older than that. +passed to `NewAuthenticationContext` is older than that, allowing +`Limits.MaxClockSkew`. **What to change:** when `InteractionRequest.HasMaxAge` is set and your user's last authentication is older than `MaxAge`, authenticate them -again before calling `Authorize`. A `max_age` of 0 asks for a fresh -authentication every time. `InteractionRequest.ACRValues` now carries +again before calling `Authorize`, and pass the time they actually +authenticated, never `time.Now()` for an existing session. A `max_age` +of 0 asks for a fresh authentication every time. `InteractionRequest.ACRValues` now carries the client's `acr_values` too, for deciding how strongly to authenticate. It's a request, not a requirement. diff --git a/client/doc.go b/client/doc.go index 1b857f34..3d40057f 100644 --- a/client/doc.go +++ b/client/doc.go @@ -5,7 +5,7 @@ // The package exposes workflow methods (BeginAuthorization, // HandleAuthorizationResponse, ExchangeCode, CompleteAuthorization, for // CIBA BeginBackchannelAuthentication and PollBackchannelAuthentication, -// and RefreshTokens to redeem a refresh token either issued) +// and RefreshTokens to redeem the refresh token either flow issued) // rather than low-level JWT, PAR or DPoP primitives — those live under internal/ // and are composed here behind a state machine that a caller cannot drive // out of order. In particular, only this package may construct request @@ -14,10 +14,11 @@ // // OpenID Connect identity (an ID token, Subject, IDTokenClaims) is // entirely optional and driven purely by what the authorization server -// actually granted, never assumed by this package: ExchangeCode and -// PollBackchannelAuthentication populate TokenSet.IDToken/Subject/ -// IDTokenClaims only when the token response actually carried an -// id_token (which, per the FAPI 2.0 authorization server this package +// actually granted, never assumed by this package: ExchangeCode, +// PollBackchannelAuthentication and RefreshTokens populate +// TokenSet.IDToken/Subject/IDTokenClaims only when the token response +// actually carried an id_token (RefreshTokens keeps the original's when +// a refresh returns none) (which, per the FAPI 2.0 authorization server this package // targets, happens exactly when "openid" was included in the granted // scope — see server's own package doc comment for that side of the // contract) and leave TokenSet.HasIDToken false otherwise, which is a @@ -53,7 +54,12 @@ // embedder registering with an authorization server (out of band; this // package has no dynamic client registration flow) doesn't have to // hand-roll RFC 7517 JWK encoding for whatever it configured -// Dependencies.Keys/Dependencies.Decryption with. +// Dependencies.Keys/Dependencies.Decryption with. ClientAttestationHeaders, +// likewise, hands an embedder the attestation-based client +// authentication headers for a request it sends itself — OpenID4VCI's +// pre-authorized_code token request, say — built by the same code as +// this package's own requests, rather than a second copy of the PoP +// format. // // client must not import server. Where both roles need the same wire // format or cryptographic operation, that logic belongs in internal/ and diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..4e01f3ec --- /dev/null +++ b/examples/README.md @@ -0,0 +1,91 @@ +# FAPIgo examples + +Six runnable demos, each a small but complete deployment of FAPIgo: +banks, apps, identity providers and APIs, each at its own `*.localhost` +host in one process on your machine. Each one tells a story from open +banking or digital identity, and each has an attack lab that tries to +break what it shows. + +| Demo | The story | Port | +|---|---|---| +| [federated-union](federated-union/README.md) | Three countries' identity federations joined into one OpenID Federation: a bank in one country signs in a citizen of another, with neither registered with the other beforehand | 8643 | +| [decoupled-checkout](decoupled-checkout/README.md) | A customer approves, on their phone, a payment or account access started on a shop's till or another app | 8644 | +| [payment-consent](payment-consent/README.md) | A web shop takes payment by bank through the FAPI 2.0 Message Signing redirect flow | 8645 | +| [payroll-run](payroll-run/README.md) | A payroll provider pays a company's staff through its bank's API, machine to machine, with mutual TLS | 8646 | +| [identity-check](identity-check/README.md) | A fintech verifies a new customer's identity by having them sign in at their bank, acting as an OpenID Provider | 8647 | +| [linked-accounts](linked-accounts/README.md) | A budgeting app links a customer's accounts for 90 days and syncs them on its own, until the customer revokes it | 8648 | + +## What each demo shows + +| Capability | Spec | federated-union | decoupled-checkout | payment-consent | payroll-run | identity-check | linked-accounts | +|---|---|:-:|:-:|:-:|:-:|:-:|:-:| +| Pushed authorization requests and the authorization code flow | [RFC 9126](https://www.rfc-editor.org/rfc/rfc9126) | ● | | ● | | ● | ● | +| Signed request objects and JARM (FAPI 2.0 Message Signing) | [FAPI 2.0 Message Signing](https://openid.net/specs/fapi-2_0-message-signing.html) | | | ● | | | | +| DPoP-bound access tokens | [RFC 9449](https://www.rfc-editor.org/rfc/rfc9449) | ● | ● | ● | | ● | ● | +| `private_key_jwt` client authentication | [OIDC Core §9](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication) | | ● | ● | | ● | ● | +| mTLS client authentication and certificate-bound tokens | [RFC 8705](https://www.rfc-editor.org/rfc/rfc8705) | | | | ● | | | +| Client certificate revocation (CRLs, intermediate CAs) | [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280) | | | | ● | | | +| Rich Authorization Requests | [RFC 9396](https://www.rfc-editor.org/rfc/rfc9396) | | ● | ● | ● | | ● | +| CIBA, poll and ping delivery | [FAPI-CIBA](https://openid.net/specs/openid-financial-api-ciba-ID1.html) | | ● | | | | | +| Client credentials grant | [RFC 6749 §4.4](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) | | | | ● | | | +| The OIDC `claims` parameter, with per-claim consent | [OIDC Core §5.5](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter) | ● | | | | ● | | +| `acr_values` and `max_age` (step-up authentication) | [OIDC Core §3.1.2.1](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) | | | | | ● | | +| Signed and encrypted ID tokens and UserInfo | [OIDC Core §5.3](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) | | | | | ● | | +| Refresh tokens, and DPoP key rotation | [RFC 6749 §6](https://www.rfc-editor.org/rfc/rfc6749#section-6) | | | | | | ● | +| Revoking a whole grant ("connected apps") | — | | | | | | ● | +| OpenID Federation: trust chains, metadata policy, automatic registration, Trust Marks | [OpenID Federation 1.0](https://openid.net/specs/openid-federation-1_0.html) | ● | | | | | | +| A resource server verifying sender-constrained tokens | [RFC 9449 §7](https://www.rfc-editor.org/rfc/rfc9449#section-7), [RFC 8705 §3](https://www.rfc-editor.org/rfc/rfc8705#section-3) | | ● | ● | ● | ● | ● | + +Each demo's README has a **What to try** walk-through, and a **How it's +built** table naming the FAPIgo API behind each piece. + +## What every demo has + +- **A console** at `https://console.localhost:/`, with a guided + tour ("Start here") and a log of the traffic between the parties. +- **An attack lab** (federated-union calls them attack scenes) that + misuses what the demo shows — a stolen token, a swapped ID token, a + tampered request, a revoked certificate — and shows each one refused, + with the reason. +- **A protocol trace** of each request and response, with the JWTs + decoded (payment-consent, payroll-run, identity-check, linked-accounts). +- **Only FAPIgo's public API.** CI checks that no demo imports + `internal/`, so nothing a demo does is out of reach of your own code. +- **Tests** that drive the whole demo end to end, attacks included. + +Every store is in memory and every key is generated at startup: these +are demos, never a template for production storage. The banks, apps, +countries and people are made up. + +## Running one + +Each demo is its own Go module that builds against this checkout of the +library: + +```sh +cd examples/payment-consent +go run ./cmd/payment-consent -open +``` + +`-open` starts Chrome (or Chromium) in a separate profile that trusts +the demo's local certificate, without a warning, and opens the console. +Each demo's README covers other browsers. + +## Ports + +Every demo has its own port, so they can all run at once. None uses +8443, which a locally running OpenID Foundation conformance suite +uses. `-port` changes it. + +| Port | Demo | +|---|---| +| 8643 | federated-union | +| 8644 | decoupled-checkout | +| 8645 | payment-consent | +| 8646 | payroll-run | +| 8647 | identity-check | +| 8648 | linked-accounts | + +`internal/demokit` is the demos' shared scaffolding: the local +certificate authority, the `*.localhost` host routing, and the browser +launch. It's for the demos only, not part of FAPIgo's API. diff --git a/examples/decoupled-checkout/README.md b/examples/decoupled-checkout/README.md index 840fdb61..9b4fe8ca 100644 --- a/examples/decoupled-checkout/README.md +++ b/examples/decoupled-checkout/README.md @@ -36,8 +36,8 @@ window for the whole demo, and only for it: Chrome accepts that key for Chrome shows a banner about an unsupported command-line flag; that's expected. Ctrl-C stops the demo. -It uses port 8644, so it can run alongside the -[federated-union demo](../federated-union/README.md) (8643). `-port` +It uses port 8644. Every demo has its own port, so they can all run at +once; [the examples overview](../README.md#ports) lists them. `-port` changes it. ### Other browsers diff --git a/examples/identity-check/README.md b/examples/identity-check/README.md index e3891bff..f911068e 100644 --- a/examples/identity-check/README.md +++ b/examples/identity-check/README.md @@ -47,8 +47,9 @@ window for the whole demo, and only for it: Chrome accepts that key for Chrome shows a banner about an unsupported command-line flag; that's expected. Ctrl-C stops the demo. -It uses port 8647, so it can run alongside the other demos (8643 to -8646). `-port` changes it. +It uses port 8647. Every demo has its own port, so they can all run at +once; [the examples overview](../README.md#ports) lists them. `-port` +changes it. ### Other browsers diff --git a/examples/linked-accounts/README.md b/examples/linked-accounts/README.md index 52c2e95d..a94cb431 100644 --- a/examples/linked-accounts/README.md +++ b/examples/linked-accounts/README.md @@ -42,8 +42,9 @@ window for the whole demo, and only for it: Chrome accepts that key for Chrome shows a banner about an unsupported command-line flag; that's expected. Ctrl-C stops the demo. -It uses port 8648, so it can run alongside the other demos (8643 to -8647). `-port` changes it. +It uses port 8648. Every demo has its own port, so they can all run at +once; [the examples overview](../README.md#ports) lists them. `-port` +changes it. ### Other browsers diff --git a/examples/payment-consent/README.md b/examples/payment-consent/README.md index f7eb6206..ab766009 100644 --- a/examples/payment-consent/README.md +++ b/examples/payment-consent/README.md @@ -38,10 +38,9 @@ window for the whole demo, and only for it: Chrome accepts that key for Chrome shows a banner about an unsupported command-line flag; that's expected. Ctrl-C stops the demo. -It uses port 8645, so it can run alongside the -[federated-union](../federated-union/README.md) (8643) and -[decoupled-checkout](../decoupled-checkout/README.md) (8644) demos. -`-port` changes it. +It uses port 8645. Every demo has its own port, so they can all run at +once; [the examples overview](../README.md#ports) lists them. `-port` +changes it. ### Other browsers diff --git a/examples/payroll-run/README.md b/examples/payroll-run/README.md index ac533edc..5179d1aa 100644 --- a/examples/payroll-run/README.md +++ b/examples/payroll-run/README.md @@ -47,10 +47,8 @@ window for the whole demo, and only for it: Chrome accepts that key for Chrome shows a banner about an unsupported command-line flag; that's expected. Ctrl-C stops the demo. -It uses port 8646, so it can run alongside the -[federated-union](../federated-union/README.md) (8643), -[decoupled-checkout](../decoupled-checkout/README.md) (8644) and -[payment-consent](../payment-consent/README.md) (8645) demos. `-port` +It uses port 8646. Every demo has its own port, so they can all run at +once; [the examples overview](../README.md#ports) lists them. `-port` changes it. ### Other browsers diff --git a/keys/doc.go b/keys/doc.go index a1538d6e..c7552732 100644 --- a/keys/doc.go +++ b/keys/doc.go @@ -46,8 +46,10 @@ // requires such a key source to declare KeySourceAssurance. // // The one exception to "production-suitable" above is keys/ephemeral, -// an in-tree, in-memory KeyManager/Decrypter/ClientKeySource set that -// always generates a fresh key rather than taking one — for local +// an in-tree, in-memory KeyManager/Decrypter/ClientKeySource set whose +// KeyManager and Decrypter always generate a fresh key rather than +// taking one, and whose ClientKeySource reads clients' registered JWK +// Sets — for local // development and testing only, never production — so integrating // server or client doesn't require writing key management from scratch // just to get something running; see its own package doc comment. diff --git a/keys/ephemeral/doc.go b/keys/ephemeral/doc.go index 1fb5d773..35fb0666 100644 --- a/keys/ephemeral/doc.go +++ b/keys/ephemeral/doc.go @@ -1,6 +1,7 @@ // Package ephemeral provides in-memory implementations of -// keys.KeyManager and keys.ClientKeySource — for local development and -// testing only. Never production — its KeyManager deliberately doesn't +// keys.KeyManager, keys.Decrypter and keys.ClientKeySource (which also +// resolves clients' encryption keys, keys.ClientEncryptionKeySource) — +// for local development and testing only. Never production — its KeyManager deliberately doesn't // implement keys.KeyCustodyAssurance, so server.AssuranceProduction and // client.AssuranceProduction reject it. // diff --git a/server/doc.go b/server/doc.go index 1f80ca01..db39436f 100644 --- a/server/doc.go +++ b/server/doc.go @@ -8,10 +8,16 @@ // RefreshAccessToken, the CIBA methods BeginBackchannelAuthentication, // LookupBackchannelInteraction (which reads a pending request back), // CompleteBackchannelAuthentication and ExchangeBackchannelAuthentication, -// RequestClientCredentialsToken, SignUserInfoResponse, Metadata, -// PublicJWKS and (for OpenID Federation) EntityConfiguration — that -// only ever consume client-generated artefacts and validate them -// against server-held state and policy. Metadata and PublicJWKS are the +// RequestClientCredentialsToken, RevokeGrant, BuildAuthorizationErrorRedirect, +// SignUserInfoResponse, Metadata, PublicJWKS and (for OpenID Federation) +// EntityConfiguration — that only ever consume client-generated +// artefacts and validate them against server-held state and policy. +// AuthenticateAttestedClient and VerifyTokenRequestBinding serve an +// embedder's own grant at the token endpoint (Config.AdditionalGrantTypes) +// — OpenID4VCI's pre-authorized_code, say — with exactly the checks, +// and the replay records, this package's own grants use; they are +// scoped to one endpoint's client authentication and sender-constraint +// checks, not generic JWT primitives. Metadata and PublicJWKS are the // exceptions: Metadata describes the server itself rather than // processing a request, and is derived entirely from Config with no // dependency I/O; PublicJWKS reports this server's own current public @@ -53,8 +59,13 @@ // - A grant can be revoked as a whole: an application that names it // when authorizing (GrantedAuthorization.GrantID) can later call // RevokeGrant, which stops its authorization code, its refresh -// token, and — at a resource.Verifier reading the same revocation -// store — every access token issued from it. +// token, an approved CIBA auth_req_id, and — at a resource.Verifier +// reading the same revocation store — every access token issued +// from it. +// - The client's authentication requirements reach the application +// (InteractionRequest.ACRValues, MaxAge and HasMaxAge), and max_age +// is enforced: CompleteAuthorization answers login_required when the +// authentication time the application reports is older. // - AuthorizationAction (from BeginAuthorization) and AuthorizationResult // (from CompleteAuthorization) are closed sum types, not structs with // optional fields, so a caller can never mistake a local error for a