A proposal to extend FedCM to allow an IDP-controlled Service Worker to intercept FedCM's credentialed requests (accounts, id_assertion, disconnect), so the IDP can attach proof-of-possession headers, apply outage resiliency, perform sovereignty-aware routing, and bridge non-FedCM identity backends.
This is a Stage 1 proposal.
- @sureshpotti
- w3c-fedid/FedCM#80
- w3c-fedid/FedCM#815 (spec PR)
The credentialed FedCM endpoints (accounts, id_assertion, disconnect) authenticate the user to the IDP using cookies. Today these requests have service-workers mode: "none", so the IDP has no programmable seam between FedCM and its own network stack. Four concrete problems result.
The cookies sent on the accounts and id_assertion calls are bearer credentials — anyone in possession of the bytes can replay them, often for the lifetime of the session (months). Modern enterprise IDPs increasingly require the cookie to be accompanied by a fresh, device-bound, proof-of-possession assertion before they will honor it. Microsoft Entra, for example, only accepts its ESTSAUTH* cookies if an x-ms-RefreshTokenCredential JWT — containing the device-wide SSO token and a server-issued nonce, signed by a TPM-backed device key — is attached to the request. Today the only way to get that header onto a FedCM-style call is via a browser extension (Chrome) or a non-standard browser API (Edge); see the Platform Authentication explainer. FedCM as specified gives no place for that proof to be attached.
Why not just "OAuth profile + DPoP at code redemption"? aaronpk's OAuth profile for FedCM shows that DPoP-bound RP tokens can already be obtained without browser changes by treating the FedCM response as an authorization code and DPoP-binding the subsequent code→token exchange. That solves RP-side token binding but does not address the IDP-cookie bearer problem (the cookie sent on the accounts call is still a plain bearer), and the extra round trip is costly for bundle apps that talk to many IDP-issued resources (e.g., Teams).
When the IDP backend is degraded — a regional incident, a partial accounts-service outage, a brief maintenance window — the FedCM call fails outright and the user sees a sign-in failure. The IDP has no opportunity to apply the resiliency patterns (regional failover, last-known-good cached account data) it routinely uses on its own surface.
Some IDPs are required (by data-residency regulations or by tenant configuration) to route a given user's authentication traffic to a specific regional service. The FedCM config.json declares a single set of endpoint URLs per IDP origin and is fetched without user context, so there is no point at which the IDP can pick a region based on the actual signed-in user before the accounts call goes out.
An IDP with an existing identity backend whose wire format does not exactly match FedCM's accounts / id_assertion shape would otherwise have to deploy a server-side translation layer in front of every FedCM endpoint. This is operationally heavy and slows adoption — especially for IDPs experimenting with FedCM alongside an existing OIDC / SAML stack.
Introduce an opt-in Identity Handler Service Worker, hosted at the IDP origin, that the user agent dispatches FedCM's credentialed calls to before they go to the network. The SW can attach proof-of-possession headers, serve cached responses during outages, rewrite outbound URLs for regional routing, or synthesize a FedCM-shaped response from a non-FedCM backend.
When the browser needs to fetch a credentialed IDP endpoint (accounts, id_assertion, disconnect), it dispatches a purpose-built IdentityRequestEvent to the registered SW via the Service Worker spec's Fire Functional Event ... on registration primitive — the same primitive Payment Handler uses for the same shape of problem (UA-initiated request, destination-origin SW, no controlling client).
enum IdentityRequestEndpoint {
"accounts",
"id_assertion",
"disconnect"
};
[Exposed=ServiceWorker]
interface IdentityRequestEvent : ExtendableEvent {
constructor(DOMString type, IdentityRequestEventInit eventInitDict);
readonly attribute IdentityRequestEndpoint endpoint;
readonly attribute Request request;
[RaisesException] undefined respondWith(Promise<Response> r);
};
dictionary IdentityRequestEventInit : ExtendableEventInit {
required IdentityRequestEndpoint endpoint;
required Request request;
};// /idp-sw.js — IDP's Identity Handler Service Worker
self.addEventListener('identityrequest', (event) => {
switch (event.endpoint) {
case 'accounts':
// Outage resiliency: try the network, fall back to a recent cache.
event.respondWith(
fetch(event.request)
.then(response => {
if (response.ok) {
caches.open('fedcm').then(c => c.put(event.request, response.clone()));
}
return response;
})
.catch(() => caches.match(event.request))
);
break;
case 'id_assertion':
// Token binding: attach a fresh DPoP proof.
event.respondWith((async () => {
const proof = await generateDPoPProof(event.request.url, 'POST');
const augmented = new Request(event.request, {
headers: { ...event.request.headers, 'DPoP': proof }
});
return fetch(augmented);
})());
break;
case 'disconnect':
// Bridge a non-FedCM revocation backend.
event.respondWith((async () => {
const body = await event.request.clone().text();
const accountId = new URLSearchParams(body).get('account_hint');
const upstream = await fetch('/internal/revoke', { method: 'POST', body });
if (!upstream.ok) return upstream;
return Response.json({ account_id: accountId });
})());
break;
}
});If the SW does not call respondWith(), rejects the promise, returns a non-OK response, returns a response of a disallowed type, or times out (default 10 seconds), the browser transparently falls back to a normal network fetch. The feature is purely additive — failures never break the FedCM flow.
Only credentialed endpoints are dispatched to the SW. Configuration endpoints stay on the UA-direct path to preserve the existing privacy boundary.
| Endpoint | SW Dispatch | Reason |
|---|---|---|
/accounts |
✅ Yes | User account data — benefits from caching and augmentation |
/token (id_assertion) |
✅ Yes | Token generation — can use DPoP, custom logic |
/disconnect |
✅ Yes | Account management — graceful error handling |
/.well-known/web-identity |
❌ No | IDP discovery — privacy protection |
/config.json |
❌ No | Configuration — privacy protection |
/client_metadata |
❌ No | RP metadata — privacy protection |
The credentialed-endpoints-only scoping is the result of explicit attack analysis on Issue #80 and cleared privacy review in the WG.
RPs require zero changes. The FedCM API contract is unchanged:
const credential = await navigator.credentials.get({
identity: {
providers: [{
configURL: "https://idp.example/config.json",
clientId: "rp_client_123",
nonce: "random_nonce_value"
}]
}
});Configuration endpoints (/.well-known, /config.json, /client_metadata) are not dispatched to the SW for privacy reasons:
- These endpoints are fetched with privacy-preserving properties (
credentials: "omit", opaque origin, no referrer). - If intercepted, the SW could correlate user identity (via cookies from its own origin) with RP identity (from
client_metadataURL parameters). - Keeping these endpoints out of SW scope preserves the privacy boundary.
The Identity Handler SW runs at the IDP's origin and sees the same information the IDP server already sees:
/accounts(GET) — no RP identity visible (opaque origin)./token(POST) — RPclient_idin the POST body (already visible to IDP server)./disconnect(POST) — RPclient_idin the POST body (already visible to IDP server).
No new information is exposed beyond what the IDP server already receives.
User consent is still required before any tokens are issued. The SW cannot bypass the FedCM consent UI — it only augments the network layer between the browser and the IDP server.
The credentialed-endpoints-only scoping was the result of explicit attack analysis on Issue #80 and cleared privacy review in the WG:
- Attack A — RP iframes IDP, then invokes FedCM. The RP embeds the IDP in an iframe so the IDP's SW (or storage) sees the RP's identity, then invokes FedCM; the IDP SW correlates the RP with the user on the accounts call. Conclusion: does not introduce new surface — browsers already partition IndexedDB and other SW-accessible storage by top-frame origin, so an iframed IDP cannot stash RP context that a top-frame FedCM SW can read.
- Attack B — uncredentialed endpoint logs the RP, credentialed endpoint reads it. If SW interception were enabled on uncredentialed endpoints (e.g.,
client_metadata), the IDP SW could record the RP identity from that call and read it back during the subsequent accounts call — combining user identity (via cookies) and RP identity in the same SW context, before the FedCM consent UI fires. Conclusion: real attack. Resolved by restricting SW interception to credentialed endpoints only (accounts, id_assertion, disconnect) and continuing to fetch configuration / metadata / well-known endpoints UA-direct with the existing privacy properties.
The Identity Handler SW is registered at the IDP origin and can only intercept requests destined for that origin. Cross-origin Service Worker interception is architecturally impossible:
✅ idp.example's SW intercepts: requests TO idp.example/accounts
❌ idp.example's SW cannot intercept: requests TO other-idp.com/accounts
❌ rp.com's SW cannot intercept: requests TO idp.example/accounts
On the UA-direct path, the browser stamps Sec-Fetch-Dest: webidentity — a forbidden header JavaScript cannot set — so the IDP can confirm the request came from the FedCM API, not from page-initiated script. In the SW path, event.request.destination is read-only and equal to "webidentity".
When the SW forwards the request via fetch(event.request), the WG aligned (in Issue #833) on not propagating Sec-Fetch-Dest: webidentity on the SW-initiated call. Instead, IDP servers verify the request source using the standard Sec-Fetch-Site: same-origin header — the SW-initiated fetch to the same IDP origin reliably carries that signal, and the IDP backend treats it as the FedCM CSRF marker on the SW path.
FedCM requests use redirect mode: "error" — the SW cannot redirect requests to different URLs. The WG agreed in Issue #833 that requiring same-origin requests from the SW effectively prevents cross-origin redirects as a risk vector, so an additional response-type filter is not required on the SW path (see Open Questions).
When the SW forwards a FedCM request, it may attach additional headers (e.g., a DPoP proof). Headers UA-set on the FedCM internal request (Accept, Sec-Fetch-*, Cookie, Origin, Referer) must not be overridden by the SW. The exact set of header names the SW is permitted to add, and the process for growing that set, is still being worked out.
The WG aligned in Issue #833 that the cookie-scope invariant can be relaxed: when the SW forwards via fetch(event.request), the IDP receives the full cookie jar — including SameSite=Lax / Strict cookies — rather than only SameSite=None. This is acceptable because the SW runs at the IDP origin and the cookies are scoped to that origin already; the relaxation is a consequence of reusing standard Service Worker fetch semantics rather than defining FedCM-specific exceptions.
The implementation uses a "skip flag" pattern for fallback:
- If SW dispatch fails → emit console warning.
- Set
skip_identity_handlerflag → bypass SW on retry. - Re-issue the same request via normal network fetch.
- Clear the flag for future requests.
This ensures that SW failures never block authentication — the flow degrades gracefully to the non-SW path.
IDP developers opt in by:
- Registering a Service Worker that handles FedCM (registration mechanism is being finalized — see Open Questions).
- Implementing an
identityrequestevent handler in their SW. - Using standard web APIs (Fetch, Cache, Crypto) within the handler, subject to the augmenting-header allowlist.
RP developers: No changes required. Completely transparent.
Debugging: Console warnings are emitted when the SW fails — e.g., "FedCM: No identity handler service worker registration found for the provider. Falling back to network."
The most conservative spec change: replace service-workers mode: "none" with "all" on FedCM's credentialed requests and let standard SW dispatch handle it. This cannot work as a spec-only change because the Service Worker spec's Handle Fetch dispatches via the request's client — and per the FedCM spec, every credentialed endpoint sets client = null. Handle Fetch has nothing to dispatch through, and the request goes straight to the network.
An alternative would be for the browser to ship a FedCM-specific dispatch layer that bypasses standard Handle Fetch entirely — look up the IDP's SW registration by URL scope, construct an internal request, invoke the SW's fetch event directly. This was prototyped and is implementable today, but the corresponding spec direction — extending Fetch / Service Worker so the spec can dispatch a FetchEvent to a SW with no controlling client — was not endorsed on the Chromium service-worker-discuss thread:
- No initiating client (Ben Kelly). The dispatch target is the destination origin's SW — the same dispatch shape used by the deprecated foreign fetch, which the SW WG removed.
- Risk to deployed SWs. Any IDP SW with a generic
fetchhandler would suddenly receive FedCM requests it was not written to handle. - Procedural asks (Dominic Farolino). A formal spec design section, security / privacy review, cross-vendor review, and TAG review were called out as prerequisites.
Introduce a purpose-built IdentityRequestEvent that the SW must explicitly listen for, dispatched via Fire Functional Event ... on registration — the same primitive used by Payment Handler for the same shape of problem. This makes the unusual dispatch model explicit at the API surface rather than overloading FetchEvent, leaves pre-existing IDP SWs unaffected, and sidesteps the "foreign fetch in disguise" critique.
SW URL declared in the IDP config.json (e.g., "identity_handler": { "service_worker": "/sw.js" } inside config.json) fails privacy — the IDP could encode RP-specific values into the URL (/sw-for-rp-A.js vs. /sw-for-rp-B.js), so the SW (and the server serving the SW script) would learn which RP the user is interacting with. The registration surface is therefore being designed outside config.json; see Open Questions.
The full design discussion is in the explainer. Several questions raised on Issue #833 have been resolved by the WG; a few remain open.
- a) Dispatch shape — dedicated event. RESOLVED in favor of a purpose-built
IdentityRequestEventrather than reusingFetchEvent. The semantics are fundamentally different — FedCM requests are UA-initiated rather than document-bound — and a dedicated interface avoids silently invoking pre-existing IDPfetchhandlers. ReusingrespondWith()as a method name is acceptable since the FedCM-specific processing logic is unambiguous in this context. (Issue #833) - Response-type restriction — relaxed. RESOLVED: the response-type check on
respondWith()is not required. Requiring the SW to issue same-origin requests when it forwards the FedCM call gives the sameredirect mode: "error"guarantee at the server layer, so an additional UA-side response-type filter would be redundant. - Cookie-scope invariant — relaxed. RESOLVED: the SW-mediated request carries the full IDP cookie jar (including
SameSite=Lax/Strict), not onlySameSite=None. This is a consequence of reusing standard Service Workerfetchsemantics; introducing FedCM-specific cookie exceptions was rejected as too invasive for the platform. - CSRF marker on the SW path. RESOLVED: the SW-initiated
fetchdoes not propagateSec-Fetch-Dest: webidentity. IDP servers should instead verify the request source viaSec-Fetch-Site: same-origin, which the SW-initiated call to the same IDP origin carries reliably. client_metadataexclusion — confirmed. RESOLVED:client_metadata(an uncredentialed request that revealsrp_client_id) must not be dispatched through the SW, since combining it with a credentialed accounts call in the same SW context would let the IDP join user identity with RP identity before consent. Enforcement is by filtering — FedCM-initiated requests bypass the standard fetch handler by default.
-
b) Service Worker registration mechanism. Two approaches are still on the table; the most recent direction is captured in PR #834 and the service_worker_model.md exploration:
- IDP-managed registration via
configURL. The IDP page calls a JS API to bind a service worker registration to a FedCMconfigURL. No SW path in the well-known file. - UA-managed registration via
/.well-known/web-identity(current direction in PR #834). The well-known file declares anidentity_handlerwith aservice_workerscript URL andscope. The UA performs the registration itself, into an isolatedStorageKey(nonce-based) so it cannot collide with any page-initiatednavigator.serviceWorker.register()for the same path. The UA fetches the well-known on every FedCM call, runs Match Service Worker Registration against the FedCM-managedStorageKey, and either dispatches to the matched registration (scheduling Soft Update in parallel) or registers + activates and then dispatches.
Sub-questions under the UA-managed direction:
- Onboarding existing users. How do already-signed-in users get the SW registered without a fresh visit to the IDP page?
- Bad-rollout recovery. Soft Update is governed by HTTP cache freshness, so a broken SW can persist until the cache expires. A control knob (e.g.,
sw_versionin the well-known) that forces a hard update bypassingCache-Controlis under discussion. - Storage pressure. Cookies survive storage eviction but the SW registration does not — leaving a dangling FedCM registration ID. Whether the UA re-registers on the next FedCM call, or relies on the user revisiting the IDP page, is open.
- Well-known reliability. Fetching the well-known on every FedCM call makes the flow sensitive to transient network errors. Aligning the well-known check cadence with the Soft Update cadence is one option under discussion.
- IDP-managed registration via
- SW URL declared in
config.json. Fails privacy — the IDP could encode RP-specific values into the URL (/sw-for-rp-A.jsvs./sw-for-rp-B.js), so the SW (and the server serving the SW script) would learn which RP the user is interacting with. - Propagating
Sec-Fetch-Dest: webidentityon SW-initiatedfetch. Would require Fetch-spec exceptions to let a service worker set a UA-reserveddestinationvalue — rejected as too invasive for the platform. Replaced by theSec-Fetch-Site: same-originserver-side check above.
This proposal builds on discussion in Issue #80, Issue #833, the WG meeting on 23 September 2025, follow-up FedCM calls on 2 June 2026 and 9 June 2026, and the Chromium service-worker-discuss thread, with feedback from FedCM editors, service-worker editors (Ben Kelly, Dominic Farolino, Yoshisato Yanagisawa), the WG privacy reviewer, and aaronpk's OAuth profile for FedCM which framed the scope of the IDP-cookie bearer problem this proposal targets.
- FedCM Specification
- Spec PR #815 — Enabling IDP Interception in FedCM Request
- Spec PR #834 — Service Worker registration strategy
- GitHub Issue #80 — SW interception for FedCM
- GitHub Issue #833 — Resolutions on dispatch, response type, cookie scope, and CSRF marker
- Explainer — identity_handler.md
- Service Worker model exploration
- WG meeting notes (23 Sept 2025)
- Chromium service-worker-discuss thread
- Service Worker Specification
- Fetch Specification
- Payment Handler API
- DPoP (RFC 9449)