Enabling IDP Interception in FedCM Request#815
Conversation
| 1. Let |processResponse| be the result of the following steps given a | ||
| <a spec=fetch for=/>response</a> |response| and |responseBody|: |
There was a problem hiding this comment.
I overlooked the incorrectness of <a spec=fetch for=/>response</a> when I wrapped it from line 1590 to 1591. I cannot easily tell what this should be, so I'm not suggesting a correction here ... but I'm flagging the need for that correction. If we can figure it out, I think it can be applied in this PR. If not, this comment should be used to start a new issue.
| <var ignore>processResponseConsumeBody</var> set to the following steps given a | ||
| <a spec=fetch for=/>response</a> |response| and |responseBody|: | ||
| 1. If |responseBody| is null or failure, set |credential| to the result of | ||
| [=create an IdentityCredentialError|creating an IdentityCredentialError=] with {} |
There was a problem hiding this comment.
| [=create an IdentityCredentialError|creating an IdentityCredentialError=] with {} | |
| [=create an IdentityCredentialError|creating an IdentityCredentialError=] with «» |
See how the Infra Standard defines list literal syntax. I see there are other pre-existing instances of this in this spec already, but let's start incrementally using the right one.
There was a problem hiding this comment.
@domfarolino — Please provide a link to "how the Infra Standard defines list literal syntax" so I and other reviewers don't need to spend time searching for it and maybe find the wrong definition. My immediate question is whether the European quotation mark chevrons («») are what is intended, and not doubled angle brackets (<< >>).
There was a problem hiding this comment.
| from |response| and |responseBody|. | ||
| 1. [=converted to an IDL value|Convert=] |json| to an {{IdentityAssertionResponse}}, |token|. | ||
| 1. If one of the previous two steps threw an exception, set |credential| to the result of | ||
| [=create an IdentityCredentialError=] with {} and |globalObject|, and return. |
There was a problem hiding this comment.
same here (and any instances below).
| |globalObject|. | ||
| 1. The user agent MAY set |credential|'s {{IdentityCredentialError/error}} based on | ||
| |response|'s [=response/status=]. For example, if the [=response/status=] is `500`, it | ||
| could set it to "server_error", and if the [=response/status=] is `503`, it could set |
There was a problem hiding this comment.
It seems a little unfortunate that this is not standardized. Has there been any discussion about doing that?
| : {{IdentityRequestEvent/endpoint}} | ||
| :: |endpoint| | ||
| : {{IdentityRequestEvent/request}} | ||
| :: |request| |
There was a problem hiding this comment.
(Continuation of my other comment, somewhere above)
I'm now realizing for the first time that we don't actually use the target IDP service worker like a normal service worker, where a request is first fetched, and then deep in Fetch, the Service Worker's "handle fetch" algorithm is called with a request that has been heavily processed, its headers set, its client/referrer/origin all resolved, etc.
Here, we're trying to sort of "mock" that by constructing a request, and just immediately firing a functional event inside the service worker passing the very-unprocessed request to the SW directly (i.e., Sec-Fetch-Dest headers were never added). But what good is it to expose this request to script, if it hasn't been processed much yet? Is it the request's URL that script will care about and want to act on?
Was there any discussion about just "fetching" the above request normally, and letting Fetch pass it off to the IDP's service worker (we could easily make it choose a FedCM-specific IDP service worker, instead of the normal one, as we've discussed elsewhere in this PR). That way we could do the normal thing of dispatching a FetchEvent on the service worker, and letting the service worker process the IDP request like any other request.
Maybe this has been discussed before and I'm off base. If so, my bad!
There was a problem hiding this comment.
I’d like to offer a different perspective on the architectural layering here. I remain convinced that leveraging the Fire Functional Event algorithm is the correct path forward for FedCM, rather than extending Handle Fetch.
My primary concern is that allowing browser-initiated, cross-origin requests to piggyback on the Handle Fetch event effectively re-opens the door to Foreign Fetch, which is a feature that was intentionally removed due to significant security and privacy risks. If we extend Handle Fetch to support client: null requests for FedCM, we create a precedent that arbitrary standards can use to bypass the Same-Origin Policy (SOP) via the Service Worker pipeline.
As we discussed previously on service-worker-discuss, the Handle Fetch algorithm should remain strictly dedicated to navigations and subresource fetches within a controlled environment. In contrast, the Fire Functional Event algorithm was designed specifically to allow the User Agent to orchestrate specialized interactions with a Service Worker without blurring these fundamental spec boundaries.
This approach keeps the Service Worker specification focused and simple, while placing the responsibility for defining FedCM-specific request/response semantics where they belong: in the FedCM specification.
I would also very much value hearing @wanderview's thoughts on whether extending Handle Fetch in this manner poses a long-term risk to the Service Worker security model.
There was a problem hiding this comment.
I haven't had time to read this PR as its quite long, but the principles you are talking about make sense to me, @yoshisatoyanagisawa. In particular, I think using Fire Functional Event is important to make sure the service worker keep-alive logic, etc, is handled properly. Bypassing that could result in the SW thread being kept alive too long which introduces privacy and abuse risks.
There was a problem hiding this comment.
I'm not opposing this design due to a layering concern. I'm raising this because it just doesn't work. The stated intent of firing the functional event in the IDP's Service Worker is so that the IDP can "forward" the identity event's fetch request, but preserve the Sec-Fetch-Dest: webidentity header, so that the IDP's server knows it came form a legitimate UA-constructed webidentity request:
Its value is a browser-constructed {{Request}} object for the [=IDP=]
endpoint. The request has its [=request/destination=] set to "webidentity", which causes the [=user agent=] to set the [:Sec-Fetch-Dest:] [=forbidden request-header=] towebidentity. The
service worker can forward this request via {{WindowOrWorkerGlobalScope/fetch()|fetch(event.request)}} to preserve the header, which the
[=IDP=] server can use to verify the request originated from the FedCM API.
But this is just not how service workers work. FetchEvent.request.destination will indeed remain webidentity, and I believe the forbidden request headers like Sec-Fetch-Dest will exist, just not be exposed to script since they're sensitive. But when you call fetch() on that request, it immediately constructs a new DOM Request object from the original request, and this does NOT copy over the destination, which means when we go back through the whole network stack with this request Sec-Fetch-Dest will not be preserved. So the current design does not accomplish the stated goals.
(@npm1 raised a similar concern about webidentity in #815 (comment) / https://github.com/w3c-fedid/FedCM/pull/815/changes#r3053129839.)
There was a problem hiding this comment.
Yea, its similar to w3c/ServiceWorker#1803
There was a problem hiding this comment.
Since the fetch destination is limited and uncontrollable from the ServiceWorker script, I think this is the reasonable choice. We might need to ensure the allowed headers are safe to use. Also, I hope the security to review the API.
There was a problem hiding this comment.
Going back to @yoshisatoyanagisawa's response above:
If the IDP can identify the requester is the Service Worker registered by the IDP, it is fine. However, without browser-enforced signals (like forbidden headers), an attacker can easily write attack code to pretend to be the Service Worker to cause CSRF.
The IDP can verify that the requestor is in the IDP's own origin (i.e., its own service worker) via forbidden headers, like Sec-Fetch-Site: same-origin. An attacker cannot write "attack code" to pretend to be the IDP's service worker when contacting the IDP's server, because it cannot forge this header. So here's how I envision it:
Fetches caught by the IDP's service worker would validate that the IdentityRequestEvent's request has a webidentity destination, that the RP could not forge. If the SW checks out the request and deems it "valid", then the SW sends a same-origin request to its own server to get the actual resource. The server checks that Sec-Fetch-Dest === same-origin to be sure that the attacker did not forge the request, and to confirm that it's talking to its own origin.
If there is no browser help to provide a non-forgeable identifier, it might not be so easy to maintain the existing security model.
So yeah, just to be clear I think the existing forbidden headers give us everything we'd need, without warping the processing model of service workers as a special case in this specification. The service worker processing model is how it is for a reason, and I think letting individual specs poke substantial holes in it for one-off use-cases is regrettable and should be avoided, especially when existing primitives line up to form an equivalent solution, which I think is the case here.
Also the whole augmented fetch thing doesn't make sense to me. The draft above references things like "§3.4 below" that don't exist, and definitions like "Allowlist headers section" that aren't filled out. @pottis please scrutinize your AI responses more thoroughly.
There was a problem hiding this comment.
@domfarolino , I intentionally left out those points because I wanted to confirm the approach first. I understand your feedback and will make sure to include all relevant information to help reviewers in the future.
Regarding your comment, "Also the whole augmented fetch thing doesn't make sense to me"—could you please elaborate? There are some limitations with fetch(…) where certain properties get reset when it's called for which i am proposing this approach.
There was a problem hiding this comment.
—could you please elaborate?
I don't know what "dpop proof" is, and I'm not sure how it fits into this proposal. What is the "augmented fetch" trying to accomplish? I'd appreciate an explanation to that before a long algorithm with incomplete definitions. For example: is the goal to preserve webidentity and Sec-Fetch-Dest from the request that the SW intercepted from the RP? If so, then why is any kind of "proof" / key / signature needed, if the existence of the forbidden header is enough? And if you're not trying to preserve webidentity and Sec-Fetch-Dest, then why do we need a special "augmented fetch" anyways, when you can just add your own (cryptographic) headers to the request that the SW forward onto its same-origin server?
There was a problem hiding this comment.
That makes sense. I initially thought a new fetch API was necessary for CSRF prevention, but if it can be reliably achieved using existing primitives like Sec-Fetch-Site: same-origin, I agree that we should avoid introducing a new API. Adding a non-normative note to guide developers on how to implement these server-side checks should be sufficient to address the CSRF concerns without adding complexity to the service worker model.
|
Discussed during the 5 May 2026 meeting. |
| is already converted in. This avoids invoking [=converted to an IDL value=] from the | ||
| calling [=in parallel=] context, which has no associated <a for="global object">realm</a>. | ||
|
|
||
| 1. Let |registration| be the result of running <a spec=service-workers>Match Service Worker |
There was a problem hiding this comment.
Technically, arbitrary "Match Service Worker Registration" algorithm caller can look up the registration for FedCM if the storage key and origin matches. Is it intended? i.e. algorithm outside of the FedCM may use the registration or unregister it.
There was a problem hiding this comment.
Yeah, I thought we were keeping a separate partition of FedCM-specific service workers so that we wouldn't collide with pre-existing service worker registrations, and so that other parts of the platform wouldn't collide with FedCM/IDP service workers.
There was a problem hiding this comment.
I am saying this because I thought FedCM ServiceWorker is intend to be isolated from the other ServiceWorkers by having its own mapping, but it looks not.
There was a problem hiding this comment.
If I understand the concerns correctly,
a) Pre-existing IDP service workers (caching/push) are automatically enrolled into FedCM as soon as a listener is added.
b) FedCM service workers can be accessed and unregistered by any caller using Match Service Worker Registration.
To separate general functionality from FedCM-specific behavior, I'm considering adding a field to ServiceWorkerRegistration:
partial dictionary RegistrationOptions { boolean acceptsFedCM = false; };
partial interface ServiceWorkerRegistration { readonly attribute boolean acceptsFedCM; };
With this change, IDP authors would register their service worker as follows:
navigator.serviceWorker.register('/sw.js', { acceptsFedCM: true });
Please share your thoughts, or let me know if you have suggestions for a better approach to achieve isolation.
Note: The current specification prefers the IDP to register the service worker
There was a problem hiding this comment.
You need a mechanism for this new kind of "acceptsFedCM" service worker to intercept webidentity requests; how would that work? When Fetch fetches webidentity requests, would it just pass a special "key" to https://w3c.github.io/ServiceWorker/#match-service-worker-registration, so that we only match "acceptsFedCM" service workers?
There was a problem hiding this comment.
@pottis I'm not convinced that adding this field fixes the issue. A proper fix might require ServiceWorkerRegistration to have a state explaining its owners (FedCM, Web, or both), but that involves updating the registration and control logic. I'd like to understand the clear rationale for opting for this complexity over simple isolation.
There was a problem hiding this comment.
What would the alternative "simple isolation" look like to you @yoshisatoyanagisawa? When would Fetch know to trigger the "FedCM service worker" vs others, and how would we know which SW to fetch/install when doing all of this for FedCM?
There was a problem hiding this comment.
After discussing this directly with @pottis, I recognize that I had been operating under the assumption of a previous registration model and overlooked the fact that it had already changed in the current design.
Correcting for that understanding, I believe the API should follow the architectural pattern used by features like the Periodic Background Sync API or the Notification API.
Specifically, I suggest extending ServiceWorkerRegistration with a dedicated interface—for example, registration.identity (an IdentityProviderManager). To enable FedCM interception, developers would be required to explicitly call registration.identity.register(configURL). This creates a one-to-one mapping between the IDP Config URL and the registration, ensuring that functional events are only dispatched to workers that have intentionally opted-in to handle that specific provider. (configURL was used since configURL might be the unique identifier for IdentityProviderConfig.)
This approach addresses the semantic mismatch of using the implicit 'Match Service Worker Registration' algorithm for internal UA requests that lack a controlled client. It also provides a clear lifecycle for unlinking via unregister(configURL) or through the removal of the parent registration.
What are your thoughts on adopting this explicit registration model?
| service worker can forward this request via {{WindowOrWorkerGlobalScope/fetch()|fetch(event.request)}} to preserve the header, which the | ||
| [=IDP=] server can use to verify the request originated from the FedCM API. |
There was a problem hiding this comment.
| service worker can forward this request via {{WindowOrWorkerGlobalScope/fetch()|fetch(event.request)}} to preserve the header, which the | |
| [=IDP=] server can use to verify the request originated from the FedCM API. | |
| service worker can forward this request via {{WindowOrWorkerGlobalScope/fetch()|fetch(event.request)}} | |
| to preserve the header, which the [=IDP=] server can use to verify whether the request | |
| originated from the FedCM API. |
| {{Event/isTrusted}} and throws an "{{InvalidStateError}}" {{DOMException}} if it is false, | ||
| so only UA-dispatched events (created via [=creating an event=]) can provide responses. | ||
|
|
||
| Each {{IdentityRequestEvent}} has a |
There was a problem hiding this comment.
nit but is it possible for the JavaScript object to have the field like this? (this is something like how to write standard, and not important for the way to implement this.)
In the past, I might have implemented the corresponding structure to deal with the situation like: https://urlpattern.spec.whatwg.org/#urlpattern-associated-url-pattern
However, I hope whom knows web standard will take another look.
There was a problem hiding this comment.
Can you clarify the concern you have @yoshisatoyanagisawa
There was a problem hiding this comment.
I thought we need a corresponding data structure to have a hidden field for the web exposed structure, and I thought we need the corresponding data structure for IdentityRequestEvent to have hidden fields while we do not provide the mapping from the Web exposed to the internal structure if we want the field hidden.
However, if we do not need such a complicated logic to realize the hidden field, it should be fine.
There was a problem hiding this comment.
Got it. The way this PR does it and the way that URLPattern does it is basically the same. Objects, including IDL objects, can have two types of "things":
- They can have "internal" members, which are denoted in spec prose by either
[[InternalSlots]], "associated" structures like you do with URLPattern, or just ordinary non-IDL "members", like https://dom.spec.whatwg.org/#abortsignal-abort-algorithms; - They can have IDL attributes and methods, which are web-exposed.
Both URLPattern and this spec are doing (1) here, and they're the same.
There was a problem hiding this comment.
ok, then let me withdraw my comment. This looks following https://dom.spec.whatwg.org/#abortsignal-dependent style.
|
|
||
| The <dfn for="IdentityRequestEvent" attribute>endpoint</dfn> attribute getter steps are to return the value | ||
| to which it was initialized. Its value indicates which [=IDP=] endpoint is being requested. The | ||
| service worker uses this to determine whether to handle the request or let the [=user agent=] fall |
There was a problem hiding this comment.
to be clear, "service worker script" instead of "service worker", right?
"service worker" sounds like that the Service Worker core logic uses this for decision making.
There was a problem hiding this comment.
Thank you for pointing that out. I had been using SW and the SW script interchangeably, but I'll make sure to clarify the distinction moving forward.
| to which it was initialized. Its value is a browser-constructed {{Request}} object for the [=IDP=] | ||
| endpoint. The request has its [=request/destination=] set to "`webidentity`", which causes the | ||
| [=user agent=] to set the [:Sec-Fetch-Dest:] [=forbidden request-header=] to `webidentity`. The | ||
| service worker can forward this request via {{WindowOrWorkerGlobalScope/fetch()|fetch(event.request)}} to preserve the header, which the |
There was a problem hiding this comment.
We may want to revise this because the fetch from the Service Worker script won't preserve the sec-fetch-destination header. I understand that the dedicated method will be provided for the purpose instead.
There was a problem hiding this comment.
Sure. Seems @domfarolino still not aligned on fetchAuguement on its purpose, once i add proposal API to spec, will update the wording.
There was a problem hiding this comment.
Given #815 (comment) it looks like we're all aligned that "augmented fetch" and preserving Sec-Fetch-Destination is not a key part of this proposal, and is probably not needed.
There was a problem hiding this comment.
Yeah, upon the discussion, I understand we agreed not to provide the additional fetch method. We just leave the developer note to ask them to check sec-fetch-site instead.
Anyway, the phrase should be updated because sec-fetch-dest won't be available.
| {{DOMException}}. | ||
| 1. [=ExtendableEvent/add lifetime promise=] |response| to [=this=]. | ||
| 1. Set [=this=]'s [=IdentityRequestEvent/respond-with called=] to true. | ||
| 1. Set [=this=]'s [=Event/stop propagation flag=]. |
There was a problem hiding this comment.
If other reviewers requested, it should be fine. However, I failed to catch why these flags should be set because events might not be propagated to anybody else.
There was a problem hiding this comment.
@domfarolino requested to capture the steps.
There was a problem hiding this comment.
This is because the following should throw:
self.onidentityevent = e => {
e.respondWith(foo);
e.respondWith(bar); // throws, since respondWith() was already called!
};To match how FetchEvent works in service workers already: https://w3c.github.io/ServiceWorker/#ref-for-fetchevent-respond-with-entered-flag.
There was a problem hiding this comment.
Thank you for the reply, I checked https://w3c.github.io/ServiceWorker/#fetch-event-respondwith Step 5, and it looks doing something similar.
Then, it is fine to leave as-is.
| it to "temporarily_unavailable". | ||
| 1. Return. | ||
| 1. Run |processToken| with |token|. | ||
| 1. Wait for |credential| to be set. |
There was a problem hiding this comment.
nit but since |credential| was set to null in L1616, the step may not wait anything. You might want to wait here while |credential| is null?
e.g. I saw ServiceWorker has yet another flag to tell the finish condition or watching a specific field like: https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
| waiting for a [=/service worker=] to become active. See [[#idp-guidance]] for how | ||
| [=IDPs=] should register and activate their [=/service worker=] to ensure reliable | ||
| interception. | ||
| 1. Let |result| be null. |
There was a problem hiding this comment.
nit but might be good to mark |result| unset or something non null, then it might be easy for the latter step to recognize the value is set.
| 1. [=Upon rejection=] of |respondWithPromise|: | ||
| 1. The user agent [=reports a warning to the console=] indicating that the | ||
| identity handler {{IdentityRequestEvent/respondWith()}} promise was rejected. | ||
| 1. Set |result| to failure. |
There was a problem hiding this comment.
may want to return in addition
| and an IDL dictionary type |dictionaryType|, | ||
| run the following steps. This returns an instance of |dictionaryType|, null, or failure. A null | ||
| result indicates the [=/service worker=] did not handle the request; a failure result indicates | ||
| the [=/service worker=] attempted to handle the request but failed. |
There was a problem hiding this comment.
For the failed case, the PR seems to treat it as the failure. Is it really intended?
cf. For the Service Worker Handle Fetch algorithm, it may return the network error, and the fetch specification will trigger the network fallback for the case instead of showing the error page. Therefore, broken ServiceWorker script would just bring the network fallback instead of complete service stop.
| [=IDP=] and [=RP=] implement and enforce appropriate security headers (such as CSP, CORS, | ||
| and the mandatory use of the `Sec-Fetch-Dest: webidentity` header). These headers are key to | ||
| and the mandatory use of the [:Sec-Fetch-Dest:] header set to `webidentity`). These headers are key to | ||
| distinguishing browser-initiated FedCM flows from arbitrary requests. |
There was a problem hiding this comment.
You may want to add a note for the service worker case.
| browser-constructed {{Request}} with [=request/destination=] set to "`webidentity`". This | ||
| causes the [=user agent=] to set the [:Sec-Fetch-Dest:] [=forbidden request-header=] to `webidentity` | ||
| on the request, which the [=IDP=] server can use to verify that the request originated from | ||
| the FedCM API. The service worker can forward this request via {{WindowOrWorkerGlobalScope/fetch()|fetch(event.request)}}. The |
There was a problem hiding this comment.
You might need to update this because it actually cannot forward the sec-fetch-dest header.
|
|
||
| ### Scope Selection ### {#idp-guidance-scope-selection} | ||
|
|
||
| [=IDPs=] are encouraged to register the [=/service worker=] with a scope that |
There was a problem hiding this comment.
I thought the FedCM's ServiceWorker is isolated from the regular web ServiceWorkers, but it looks not?
There was a problem hiding this comment.
replied in this comment : #815 (comment)
|
Hey @pottis, thanks for working on this and pouring so much effort into it, however I think we should step back a little bit and discuss some things at a higher-level. This PR is close to 400 lines long and we're 300 comments deep, and @samuelgoto and I are noticing that there's no OP1, no explainer, no written design requirements, and the farther we go down a few rabbit holes, the more deep design alterations get proposed. I think we're all quite motivated to help unblock major partners adopting the FedCM specification, but fundamentally this deep into a pull request is not the best place for us to have such deep design discussions, which is why threads like #815 (comment) are getting a unruly and hard to resolve. We think it'd be best to step back a bit and try and draft an explainer for this work. This can probably be a distillation of the parts of #80 that stuck, as well as discussion of how this relates to #787, and what resolutions if any came out of previous WG meetings about this feature (since I got pulled into all of this outside and after all of the meetings, as far as I can tell). I'm happy to help out with the explainer too. Footnotes
|
|
This has been added to the agenda for Tuesday, 19 May 2026. From the email to the lists: PR #815 proposes a mechanism that would allow Identity Providers to handle certain FedCM requests using a Service Worker-based mechanism. The motivating use cases include request signing / proof-of-possession patterns such as DPoP, operational failover, geographic or jurisdiction-specific routing, caching during outages, and integration with legacy identity systems. The discussion has raised detailed questions across FedCM request processing, Service Worker dispatch, Fetch semantics, privacy, and IDP security expectations. Some of the detailed review appears to depend on higher-level direction from the WG. For example, we should clarify what privacy and security properties need to be accounted for in the IdP Interception proposal. If a FedCM request is handled by an IDP-controlled Service Worker rather than sent directly by the browser, what properties must remain true? For example, what may the handler learn compared with the current browser-direct request path? Is fallback to the ordinary network path always acceptable, or are there cases where the IDP must be able to fail closed? Speaking as chair, I would like to separate those direction-setting questions from line-by-line spec review. I am not taking a position on the proposal, but I would like to make sure the CG and WG has a shared understanding of the problem and the boundaries so we can make an explicit decision on what needs to be in the PR. |
The current FedCM specification defines a browser-mediated flow where the browser makes direct HTTP requests to IDP endpoints. This PR implements a foundational capability allowing Identity Providers (IDPs) to intercept and handle FedCM (Federated Credential Management) requests through Service Workers. This feature unlocks advanced security and operational patterns that were previously impossible in the FedCM ecosystem.
Having this feature, it will address some of the key-issue from enterprise identity providers.
Key issues:
Links:
Issue #80
Explainer : https://github.com/pottis/FedCM/blob/4499cd7593e8f39b588ff3a6cca90e7ffd68beb8/explorations/identity_handler.md
Preview | Diff