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