Minor Changes
-
#335
c1a8a40Thanks @mattzcarey! - Add helpers for authorization servers that sign users in through another OAuth provider, implementing the MCP security best practices' confused-deputy protections.beginConsent()/approveConsent()/denyConsent()run a consent page whose handle is bound to the browser by a__Host-cookie, single-use and valid for ten minutes, withframe-ancestors 'none'andX-Frame-Options: DENY.approveConsent({ scope })takes the scopes the user chose on the page, fewer or more than requested, each inscopesSupported.approveConsent(…, { remember: { secret } })andisConsentRemembered()remember an approval per call in a signed cookie bound to the client, redirect URI and resource, covering only the approved scopes. Withoutremember, consent is asked every time.beginUpstream()/finishUpstream()store the approved request server-side, create the third party'sstateonly after consent, bind it to the browser, and return it once at the callback with yourdata(such as a PKCE verifier).cookiePrefixrenames the cookies; it must start with__Host-.- An
OAuthError('invalid_grant')thrown fromtokenExchangeCallbacknow revokes the grant the callback ran for, with its access tokens, before answering.invalid_grantcan never recover (RFC 6749 §5.2), so the client re-authorizes instead of retrying, and outstanding access tokens stop working immediately. Throwtemporarily_unavailablefor transient upstream failures to keep the grant.
Transactions are stored in
OAUTH_KVundertransaction:{sha256(handle)}, encrypted with a key derived from the handle, with a ten-minute TTL; each has its own binding cookie, so concurrent authorizations in one browser don't collide.remember.subjectbinds a remembered approval to the signed-in user.denyConsent()returns theaccess_deniedredirect back to the client with itsstateandiss. Seedocs/consent-page.mdanddocs/upstream-sign-in.md.
Patch Changes
-
#323
a1225a7Thanks @chiliec! -updateClient()no longer appliesclientRegistrationTTLto clients created throughcreateClient().It re-applied the TTL to every client it wrote, so updating a pre-registered client silently gave it an expiry. The TTL is now preserved only for records stamped with
registrationExpiresAt; other records are written permanently. Dynamic registrations written before that stamp existed therefore become permanent when updated.