Repository navigation
Version Packages - #272
Open
github-actions[bot] wants to merge 1 commit into
Open
Version Packages#272github-actions[bot] wants to merge 1 commit into
github-actions[bot] wants to merge 1 commit into
Conversation
github-actions
Bot
force-pushed
the
changeset-release/main
branch
2 times, most recently
from
November 5, 2026 01:16
d322a32 to
b654854
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
November 5, 2026 18:09
b654854 to
5ff1e06
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@seamless-auth/core@0.25.0
Minor Changes
b4224ef: Deliver enrollment invites through adopter messaging (feat: deliver enrollment invites through adopter messaging #183). With
messagingconfigured,POST /admin/enrollment/invitesnow asks the auth API for external delivery (x-seamless-auth-delivery-mode: external, with theseamless-auth-external-deliveryservice token) and sends each invite inresults[].deliverythroughdeliverAuthMessage, sohandlers.sendEnrollmentInviteEmail,overrides.enrollmentInviteEmailor the email transport send it instead of the auth API's mail system. Withoutmessagingthe route is unchanged.The response no longer carries
results[].delivery, so recipient addresses and sign-in links are not echoed to the browser. A recipient whose delivery throws is reported as{ "status": "skipped", "reason": "delivery_failed" }andsentandskippedare adjusted; the other recipients are still sent and the route answers 200. The auth API has already recorded that recipient as invited, so repeating the call within a day reports them asrecently_invited.b4224ef: Keep the session cookies when a refresh fails without the auth API refusing it, matching the Go adapter (Run refresh rotation detached from the request and keep cookies on transport errors seamless-auth-go#22). The silent refresh on the auth routes and cookie-transport
POST /refreshused to clear the access and refresh cookies on any failure, so a network blip or an auth API restart signed the browser out.The cookies are now cleared only when the auth API answers the refresh with 401, or the refresh cookie cannot be read. Those keep their current answers (
401 {"error":"Refresh failed"}on a silent refresh,401 {"error":"invalid_refresh_token"}onPOST /refresh). Otherwise the cookies stay and:502 {"error":"upstream_unavailable"};502 {"error":"upstream_unavailable"}.POST /refreshanswered these with 500 before.Core adds
refreshCookieSession, which reports how a cookie rotation ended, andfailureBody.refreshAccessTokenkeeps its signature. In-flight sharing and the 5 second window for a completed cookie rotation are unchanged, and only a successful rotation is kept. No request's abort signal reaches the upstream call, so a caller that goes away does not cancel a rotation other callers share.662b6aa: Let a verify-only resource server require a user rather than a client credentials token (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). The Express and Fastify
requireAuthguards fromcreateResourceServertake{ allowClients: false }, the Next.jsprotectandauthenticaterequirements takeallowClients: false, and core'sauthenticate(authorization, { allowClients: false })does the same. A verified token whose user hasisClientset is then refused with403 {"error":"user_required"}and noWWW-Authenticateheader, the same shape asinsufficient_role. A token that fails verification still gets401first.allowClientsdefaults totrue, so existing routes are unchanged.The shared verify-only vectors gain
is_clienton each accepted token and auser_required_requestslist. Both are additive, so the Go, Python and Rust copies keep passing until those adapters implement the option.Patch Changes
626dcc6: The bundled fallback adapter manifest is the one auth API 0.22.0 publishes, adding the admin application link routes.
662b6aa: Run the origin check in the Express and Fastify
requireAuthguards, as the Next.js guards and the Go, Python and Rust adapters already do (Require a JSON content type on unsafe methods whatever the SameSite setting #256), and refuse a literalnullOriginin every case.Requests now refused:
requireAuth(and sorequireRolebehind it) answer a state-changing request (anything butGET,HEADandOPTIONS) with403 {"error":"cross_site_request_blocked"}whenSec-Fetch-Siteiscross-siteor theOriginis a literalnull. A newallowedOriginsoption, which takes the same list as the auth router, also requires a listedOriginfor asame-siterequest or one with noSec-Fetch-Site; an empty list is enforced. A bearer request is not checked, and the guards ask for no JSON content type.checkOriginrefuses a literalnullOriginwhateverSec-Fetch-SiteandallowedOriginssay. It used to pass one sent withSec-Fetch-Site: same-origin,same-siteornone. This applies to the auth routes of every adapter and to the Next.js guards.662b6aa: Stop replaying a completed bearer refresh rotation to later callers (Stop replaying a completed refresh rotation to later callers #258). After a successful rotation, the adapter used to hand the new session to any caller presenting the same old refresh cookie or bearer refresh token for the next 5 seconds, without calling the auth API, so a stolen bearer refresh token replayed in that window got the new tokens and the auth API's reuse detection never saw it.
Bearer transport now shares only a rotation still in flight. A bearer client that presents a refresh token after its rotation completed goes to the auth API, which treats it as a replay and answers
401 {"error":"refresh_token_reused"}.Cookie transport keeps the 5 second window, because browser tabs share one refresh cookie and a second tab sending the old cookie just after the first tab's rotation is expected. Within 5 seconds of a successful rotation the old refresh cookie gets the same rotated session; after that it goes to the auth API as a replay. A failed rotation is never kept, and the window is keyed on a hash of the cookie.
662b6aa: Mark client credentials tokens on the resource server user (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). A user verified by
createResourceServernow carriesisClient,truewhensubequalsclient_id(a machine acting for itself, whoseidis a client id rather than a user id) andfalseotherwise. Code that builds aResourceServerUserby hand, such as a test double, needs the new field.0c2169d: Depend on
@seamless-auth/types^0.36.0, which adds the session exchange contract.662b6aa: Close a form-post CSRF gap on the auth routes (Require a JSON content type on unsafe methods whatever the SameSite setting #256). A body-less form
POSTfrom a sibling subdomain, or from a browser that sends no fetch metadata, could reach cookie-authenticated routes such as/recovery-codeswhen cookies wereSameSite=LaxorStrict.Requests now refused:
SameSitesetting. A state-changing request withSec-Fetch-Site: cross-siteanswers403 {"error":"cross_site_request_blocked"}underLaxandStricttoo, andallowedOriginsis enforced forsame-siteand metadata-free requests under every setting. An emptyallowedOrigins: []counts as configured. This also applies to the Next.jsrequireAuthandrequireRoleguards.Content-Type: application/jsonor carry anx-seamless-auth-transportheader (any value), even with no body, or it answers415 {"error":"unsupported_media_type"}.POST /refresh,DELETE /logoutandDELETE /logout/allare exempt for now, because the current browser client sends them bare; they still get the origin check. A client of your own that sends a body-lessPOST,PUT,PATCHorDELETEwith no content type must add one of the two.Fastify accepts an empty body under
Content-Type: application/jsonon the plugin's routes, and Express no longer reads one as{}, so a body-less JSON request forwards no body on all three adapters.checkContentTypeis exported from@seamless-auth/core.checkOriginignores itscookieSecureandcookieSameSiteinputs.@seamless-auth/express@0.25.0
Minor Changes
b4224ef: Deliver enrollment invites through adopter messaging (feat: deliver enrollment invites through adopter messaging #183). With
messagingconfigured,POST /admin/enrollment/invitesnow asks the auth API for external delivery (x-seamless-auth-delivery-mode: external, with theseamless-auth-external-deliveryservice token) and sends each invite inresults[].deliverythroughdeliverAuthMessage, sohandlers.sendEnrollmentInviteEmail,overrides.enrollmentInviteEmailor the email transport send it instead of the auth API's mail system. Withoutmessagingthe route is unchanged.The response no longer carries
results[].delivery, so recipient addresses and sign-in links are not echoed to the browser. A recipient whose delivery throws is reported as{ "status": "skipped", "reason": "delivery_failed" }andsentandskippedare adjusted; the other recipients are still sent and the route answers 200. The auth API has already recorded that recipient as invited, so repeating the call within a day reports them asrecently_invited.b4224ef: Keep the session cookies when a refresh fails without the auth API refusing it, matching the Go adapter (Run refresh rotation detached from the request and keep cookies on transport errors seamless-auth-go#22). The silent refresh on the auth routes and cookie-transport
POST /refreshused to clear the access and refresh cookies on any failure, so a network blip or an auth API restart signed the browser out.The cookies are now cleared only when the auth API answers the refresh with 401, or the refresh cookie cannot be read. Those keep their current answers (
401 {"error":"Refresh failed"}on a silent refresh,401 {"error":"invalid_refresh_token"}onPOST /refresh). Otherwise the cookies stay and:502 {"error":"upstream_unavailable"};502 {"error":"upstream_unavailable"}.POST /refreshanswered these with 500 before.Core adds
refreshCookieSession, which reports how a cookie rotation ended, andfailureBody.refreshAccessTokenkeeps its signature. In-flight sharing and the 5 second window for a completed cookie rotation are unchanged, and only a successful rotation is kept. No request's abort signal reaches the upstream call, so a caller that goes away does not cancel a rotation other callers share.662b6aa: Let a verify-only resource server require a user rather than a client credentials token (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). The Express and Fastify
requireAuthguards fromcreateResourceServertake{ allowClients: false }, the Next.jsprotectandauthenticaterequirements takeallowClients: false, and core'sauthenticate(authorization, { allowClients: false })does the same. A verified token whose user hasisClientset is then refused with403 {"error":"user_required"}and noWWW-Authenticateheader, the same shape asinsufficient_role. A token that fails verification still gets401first.allowClientsdefaults totrue, so existing routes are unchanged.The shared verify-only vectors gain
is_clienton each accepted token and auser_required_requestslist. Both are additive, so the Go, Python and Rust copies keep passing until those adapters implement the option.Patch Changes
662b6aa: Stop forwarding a client-chosen IP when Express
trust proxytrusts every address in a form other thantrue(Detect any trust-everything proxy setting in Express before forwarding the client IP #257). The adapter used to refuse onlytrust proxy === true, so a setting such as"0.0.0.0/1, 128.0.0.0/1"or a function that always returnstruelet a caller pick thex-seamless-client-ipthe auth API uses for per-IP rate limits and audit records. The adapter now asks the compiled trust function about documentation addresses, as the Fastify adapter does, and in that case logs a warning and forwards no client IP. A hop count or a named proxy address or subnet forwards as before.662b6aa: Run the origin check in the Express and Fastify
requireAuthguards, as the Next.js guards and the Go, Python and Rust adapters already do (Require a JSON content type on unsafe methods whatever the SameSite setting #256), and refuse a literalnullOriginin every case.Requests now refused:
requireAuth(and sorequireRolebehind it) answer a state-changing request (anything butGET,HEADandOPTIONS) with403 {"error":"cross_site_request_blocked"}whenSec-Fetch-Siteiscross-siteor theOriginis a literalnull. A newallowedOriginsoption, which takes the same list as the auth router, also requires a listedOriginfor asame-siterequest or one with noSec-Fetch-Site; an empty list is enforced. A bearer request is not checked, and the guards ask for no JSON content type.checkOriginrefuses a literalnullOriginwhateverSec-Fetch-SiteandallowedOriginssay. It used to pass one sent withSec-Fetch-Site: same-origin,same-siteornone. This applies to the auth routes of every adapter and to the Next.js guards.662b6aa: Stop replaying a completed bearer refresh rotation to later callers (Stop replaying a completed refresh rotation to later callers #258). After a successful rotation, the adapter used to hand the new session to any caller presenting the same old refresh cookie or bearer refresh token for the next 5 seconds, without calling the auth API, so a stolen bearer refresh token replayed in that window got the new tokens and the auth API's reuse detection never saw it.
Bearer transport now shares only a rotation still in flight. A bearer client that presents a refresh token after its rotation completed goes to the auth API, which treats it as a replay and answers
401 {"error":"refresh_token_reused"}.Cookie transport keeps the 5 second window, because browser tabs share one refresh cookie and a second tab sending the old cookie just after the first tab's rotation is expected. Within 5 seconds of a successful rotation the old refresh cookie gets the same rotated session; after that it goes to the auth API as a replay. A failed rotation is never kept, and the window is keyed on a hash of the cookie.
662b6aa: Mark client credentials tokens on the resource server user (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). A user verified by
createResourceServernow carriesisClient,truewhensubequalsclient_id(a machine acting for itself, whoseidis a client id rather than a user id) andfalseotherwise. Code that builds aResourceServerUserby hand, such as a test double, needs the new field.662b6aa: Close a form-post CSRF gap on the auth routes (Require a JSON content type on unsafe methods whatever the SameSite setting #256). A body-less form
POSTfrom a sibling subdomain, or from a browser that sends no fetch metadata, could reach cookie-authenticated routes such as/recovery-codeswhen cookies wereSameSite=LaxorStrict.Requests now refused:
SameSitesetting. A state-changing request withSec-Fetch-Site: cross-siteanswers403 {"error":"cross_site_request_blocked"}underLaxandStricttoo, andallowedOriginsis enforced forsame-siteand metadata-free requests under every setting. An emptyallowedOrigins: []counts as configured. This also applies to the Next.jsrequireAuthandrequireRoleguards.Content-Type: application/jsonor carry anx-seamless-auth-transportheader (any value), even with no body, or it answers415 {"error":"unsupported_media_type"}.POST /refresh,DELETE /logoutandDELETE /logout/allare exempt for now, because the current browser client sends them bare; they still get the origin check. A client of your own that sends a body-lessPOST,PUT,PATCHorDELETEwith no content type must add one of the two.Fastify accepts an empty body under
Content-Type: application/jsonon the plugin's routes, and Express no longer reads one as{}, so a body-less JSON request forwards no body on all three adapters.checkContentTypeis exported from@seamless-auth/core.checkOriginignores itscookieSecureandcookieSameSiteinputs.Updated dependencies [626dcc6]
Updated dependencies [b4224ef]
Updated dependencies [662b6aa]
Updated dependencies [b4224ef]
Updated dependencies [662b6aa]
Updated dependencies [662b6aa]
Updated dependencies [662b6aa]
Updated dependencies [0c2169d]
Updated dependencies [662b6aa]
@seamless-auth/fastify@0.16.0
Minor Changes
b4224ef: Deliver enrollment invites through adopter messaging (feat: deliver enrollment invites through adopter messaging #183). With
messagingconfigured,POST /admin/enrollment/invitesnow asks the auth API for external delivery (x-seamless-auth-delivery-mode: external, with theseamless-auth-external-deliveryservice token) and sends each invite inresults[].deliverythroughdeliverAuthMessage, sohandlers.sendEnrollmentInviteEmail,overrides.enrollmentInviteEmailor the email transport send it instead of the auth API's mail system. Withoutmessagingthe route is unchanged.The response no longer carries
results[].delivery, so recipient addresses and sign-in links are not echoed to the browser. A recipient whose delivery throws is reported as{ "status": "skipped", "reason": "delivery_failed" }andsentandskippedare adjusted; the other recipients are still sent and the route answers 200. The auth API has already recorded that recipient as invited, so repeating the call within a day reports them asrecently_invited.b4224ef: Keep the session cookies when a refresh fails without the auth API refusing it, matching the Go adapter (Run refresh rotation detached from the request and keep cookies on transport errors seamless-auth-go#22). The silent refresh on the auth routes and cookie-transport
POST /refreshused to clear the access and refresh cookies on any failure, so a network blip or an auth API restart signed the browser out.The cookies are now cleared only when the auth API answers the refresh with 401, or the refresh cookie cannot be read. Those keep their current answers (
401 {"error":"Refresh failed"}on a silent refresh,401 {"error":"invalid_refresh_token"}onPOST /refresh). Otherwise the cookies stay and:502 {"error":"upstream_unavailable"};502 {"error":"upstream_unavailable"}.POST /refreshanswered these with 500 before.Core adds
refreshCookieSession, which reports how a cookie rotation ended, andfailureBody.refreshAccessTokenkeeps its signature. In-flight sharing and the 5 second window for a completed cookie rotation are unchanged, and only a successful rotation is kept. No request's abort signal reaches the upstream call, so a caller that goes away does not cancel a rotation other callers share.662b6aa: Let a verify-only resource server require a user rather than a client credentials token (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). The Express and Fastify
requireAuthguards fromcreateResourceServertake{ allowClients: false }, the Next.jsprotectandauthenticaterequirements takeallowClients: false, and core'sauthenticate(authorization, { allowClients: false })does the same. A verified token whose user hasisClientset is then refused with403 {"error":"user_required"}and noWWW-Authenticateheader, the same shape asinsufficient_role. A token that fails verification still gets401first.allowClientsdefaults totrue, so existing routes are unchanged.The shared verify-only vectors gain
is_clienton each accepted token and auser_required_requestslist. Both are additive, so the Go, Python and Rust copies keep passing until those adapters implement the option.Patch Changes
662b6aa: Run the origin check in the Express and Fastify
requireAuthguards, as the Next.js guards and the Go, Python and Rust adapters already do (Require a JSON content type on unsafe methods whatever the SameSite setting #256), and refuse a literalnullOriginin every case.Requests now refused:
requireAuth(and sorequireRolebehind it) answer a state-changing request (anything butGET,HEADandOPTIONS) with403 {"error":"cross_site_request_blocked"}whenSec-Fetch-Siteiscross-siteor theOriginis a literalnull. A newallowedOriginsoption, which takes the same list as the auth router, also requires a listedOriginfor asame-siterequest or one with noSec-Fetch-Site; an empty list is enforced. A bearer request is not checked, and the guards ask for no JSON content type.checkOriginrefuses a literalnullOriginwhateverSec-Fetch-SiteandallowedOriginssay. It used to pass one sent withSec-Fetch-Site: same-origin,same-siteornone. This applies to the auth routes of every adapter and to the Next.js guards.662b6aa: Stop replaying a completed bearer refresh rotation to later callers (Stop replaying a completed refresh rotation to later callers #258). After a successful rotation, the adapter used to hand the new session to any caller presenting the same old refresh cookie or bearer refresh token for the next 5 seconds, without calling the auth API, so a stolen bearer refresh token replayed in that window got the new tokens and the auth API's reuse detection never saw it.
Bearer transport now shares only a rotation still in flight. A bearer client that presents a refresh token after its rotation completed goes to the auth API, which treats it as a replay and answers
401 {"error":"refresh_token_reused"}.Cookie transport keeps the 5 second window, because browser tabs share one refresh cookie and a second tab sending the old cookie just after the first tab's rotation is expected. Within 5 seconds of a successful rotation the old refresh cookie gets the same rotated session; after that it goes to the auth API as a replay. A failed rotation is never kept, and the window is keyed on a hash of the cookie.
662b6aa: Mark client credentials tokens on the resource server user (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). A user verified by
createResourceServernow carriesisClient,truewhensubequalsclient_id(a machine acting for itself, whoseidis a client id rather than a user id) andfalseotherwise. Code that builds aResourceServerUserby hand, such as a test double, needs the new field.662b6aa: Close a form-post CSRF gap on the auth routes (Require a JSON content type on unsafe methods whatever the SameSite setting #256). A body-less form
POSTfrom a sibling subdomain, or from a browser that sends no fetch metadata, could reach cookie-authenticated routes such as/recovery-codeswhen cookies wereSameSite=LaxorStrict.Requests now refused:
SameSitesetting. A state-changing request withSec-Fetch-Site: cross-siteanswers403 {"error":"cross_site_request_blocked"}underLaxandStricttoo, andallowedOriginsis enforced forsame-siteand metadata-free requests under every setting. An emptyallowedOrigins: []counts as configured. This also applies to the Next.jsrequireAuthandrequireRoleguards.Content-Type: application/jsonor carry anx-seamless-auth-transportheader (any value), even with no body, or it answers415 {"error":"unsupported_media_type"}.POST /refresh,DELETE /logoutandDELETE /logout/allare exempt for now, because the current browser client sends them bare; they still get the origin check. A client of your own that sends a body-lessPOST,PUT,PATCHorDELETEwith no content type must add one of the two.Fastify accepts an empty body under
Content-Type: application/jsonon the plugin's routes, and Express no longer reads one as{}, so a body-less JSON request forwards no body on all three adapters.checkContentTypeis exported from@seamless-auth/core.checkOriginignores itscookieSecureandcookieSameSiteinputs.Updated dependencies [626dcc6]
Updated dependencies [b4224ef]
Updated dependencies [662b6aa]
Updated dependencies [b4224ef]
Updated dependencies [662b6aa]
Updated dependencies [662b6aa]
Updated dependencies [662b6aa]
Updated dependencies [0c2169d]
Updated dependencies [662b6aa]
@seamless-auth/nextjs@0.9.0
Minor Changes
b4224ef: Deliver enrollment invites through adopter messaging (feat: deliver enrollment invites through adopter messaging #183). With
messagingconfigured,POST /admin/enrollment/invitesnow asks the auth API for external delivery (x-seamless-auth-delivery-mode: external, with theseamless-auth-external-deliveryservice token) and sends each invite inresults[].deliverythroughdeliverAuthMessage, sohandlers.sendEnrollmentInviteEmail,overrides.enrollmentInviteEmailor the email transport send it instead of the auth API's mail system. Withoutmessagingthe route is unchanged.The response no longer carries
results[].delivery, so recipient addresses and sign-in links are not echoed to the browser. A recipient whose delivery throws is reported as{ "status": "skipped", "reason": "delivery_failed" }andsentandskippedare adjusted; the other recipients are still sent and the route answers 200. The auth API has already recorded that recipient as invited, so repeating the call within a day reports them asrecently_invited.b4224ef: Keep the session cookies when a refresh fails without the auth API refusing it, matching the Go adapter (Run refresh rotation detached from the request and keep cookies on transport errors seamless-auth-go#22). The silent refresh on the auth routes and cookie-transport
POST /refreshused to clear the access and refresh cookies on any failure, so a network blip or an auth API restart signed the browser out.The cookies are now cleared only when the auth API answers the refresh with 401, or the refresh cookie cannot be read. Those keep their current answers (
401 {"error":"Refresh failed"}on a silent refresh,401 {"error":"invalid_refresh_token"}onPOST /refresh). Otherwise the cookies stay and:502 {"error":"upstream_unavailable"};502 {"error":"upstream_unavailable"}.POST /refreshanswered these with 500 before.Core adds
refreshCookieSession, which reports how a cookie rotation ended, andfailureBody.refreshAccessTokenkeeps its signature. In-flight sharing and the 5 second window for a completed cookie rotation are unchanged, and only a successful rotation is kept. No request's abort signal reaches the upstream call, so a caller that goes away does not cancel a rotation other callers share.662b6aa: Let a verify-only resource server require a user rather than a client credentials token (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). The Express and Fastify
requireAuthguards fromcreateResourceServertake{ allowClients: false }, the Next.jsprotectandauthenticaterequirements takeallowClients: false, and core'sauthenticate(authorization, { allowClients: false })does the same. A verified token whose user hasisClientset is then refused with403 {"error":"user_required"}and noWWW-Authenticateheader, the same shape asinsufficient_role. A token that fails verification still gets401first.allowClientsdefaults totrue, so existing routes are unchanged.The shared verify-only vectors gain
is_clienton each accepted token and auser_required_requestslist. Both are additive, so the Go, Python and Rust copies keep passing until those adapters implement the option.Patch Changes
662b6aa: Stop replaying a completed bearer refresh rotation to later callers (Stop replaying a completed refresh rotation to later callers #258). After a successful rotation, the adapter used to hand the new session to any caller presenting the same old refresh cookie or bearer refresh token for the next 5 seconds, without calling the auth API, so a stolen bearer refresh token replayed in that window got the new tokens and the auth API's reuse detection never saw it.
Bearer transport now shares only a rotation still in flight. A bearer client that presents a refresh token after its rotation completed goes to the auth API, which treats it as a replay and answers
401 {"error":"refresh_token_reused"}.Cookie transport keeps the 5 second window, because browser tabs share one refresh cookie and a second tab sending the old cookie just after the first tab's rotation is expected. Within 5 seconds of a successful rotation the old refresh cookie gets the same rotated session; after that it goes to the auth API as a replay. A failed rotation is never kept, and the window is keyed on a hash of the cookie.
662b6aa: Mark client credentials tokens on the resource server user (Mark client credentials tokens on the resource server user and verify the ephemeral token at registration #260). A user verified by
createResourceServernow carriesisClient,truewhensubequalsclient_id(a machine acting for itself, whoseidis a client id rather than a user id) andfalseotherwise. Code that builds aResourceServerUserby hand, such as a test double, needs the new field.0c2169d: Depend on
@seamless-auth/types^0.36.0, which adds the session exchange contract.662b6aa: Close a form-post CSRF gap on the auth routes (Require a JSON content type on unsafe methods whatever the SameSite setting #256). A body-less form
POSTfrom a sibling subdomain, or from a browser that sends no fetch metadata, could reach cookie-authenticated routes such as/recovery-codeswhen cookies wereSameSite=LaxorStrict.Requests now refused:
SameSitesetting. A state-changing request withSec-Fetch-Site: cross-siteanswers403 {"error":"cross_site_request_blocked"}underLaxandStricttoo, andallowedOriginsis enforced forsame-siteand metadata-free requests under every setting. An emptyallowedOrigins: []counts as configured. This also applies to the Next.jsrequireAuthandrequireRoleguards.Content-Type: application/jsonor carry anx-seamless-auth-transportheader (any value), even with no body, or it answers415 {"error":"unsupported_media_type"}.POST /refresh,DELETE /logoutandDELETE /logout/allare exempt for now, because the current browser client sends them bare; they still get the origin check. A client of your own that sends a body-lessPOST,PUT,PATCHorDELETEwith no content type must add one of the two.Fastify accepts an empty body under
Content-Type: application/jsonon the plugin's routes, and Express no longer reads one as{}, so a body-less JSON request forwards no body on all three adapters.checkContentTypeis exported from@seamless-auth/core.checkOriginignores itscookieSecureandcookieSameSiteinputs.Updated dependencies [626dcc6]
Updated dependencies [b4224ef]
Updated dependencies [662b6aa]
Updated dependencies [b4224ef]
Updated dependencies [662b6aa]
Updated dependencies [662b6aa]
Updated dependencies [662b6aa]
Updated dependencies [0c2169d]
Updated dependencies [662b6aa]