FedCM support
Search terms you've used
FedCM
Impacted environment
In which environment would the proposed feature apply ?
- [x] The browser
- [ ] Node.js
- [ ] Other (please specify): ...
- [ ] I'm not sure.
Feature suggestion
Add support for FedCM (FPWD), which is on the REC track in FedID WG
Expected functionality/enhancement
The best example of how it could work with Solid-OIDC might be how it was implemented for IndieAuth. https://indieweb.org/FedCM_for_IndieAuth
It can already be tested in Chrome behind flags.
There is also Lightweight FedCM proposal which works in Firefox nightly. IndieAuth should also have it figured out soon.
Actual functionality/enhancement
The user will not have to enter the URL of their OP/IDP, not even their WebID. They will select their registered IDP from a small widget displayed by the browser.
Use Cases
Improved UX in the web browser.
Additional information
@thhck have done a prototype for CSS https://github.com/w3c-fedid/idp-registration/issues/2#issuecomment-2334798424 I will coordinate with him to land everything that's needed in CSS.
@acoburn is also familiar with FedCM progress.
This feature is now tracked as IdP Registration API and needs implementation feedback and interest signals.
Hi @elf-pavlik , thanks for opening this. Support for FedCM would be an interesting addition to this library indeed.
We're working on this in https://github.com/Liquid-Surf/fedcm-demo and https://github.com/solid-contrib/pivot/issues/34 Not much needed from the solid-client-authn-js side, as you can see from https://github.com/Liquid-Surf/fedcm-demo/blob/main/packages/client/src/solid.js which is just 66 lines of code.
Liquid-Surf demo seems to pass the access token through the FedCM API. Instead, it probably should pass the authorization code, similar to https://indieweb.org/FedCM_for_IndieAuth#navigator.credentials.get
This way, frontends can also work with backend clients, like the node package in this repo, and get the authorization code from the front end.
@NSeydoux can you think of the simplest way to accomplish a similar flow to the one in IndieAuth+FedCM?
- frontend request from the node backend PKCE
code_challenge - frontend passes to the backend the authorization code
- node auth module from this repo continues the flow using that authorization code, ensuring that it's used with the initial
code_challenge
I have updated my fedcm demo, which is now compatible with this library
You can test it here: https://fedcm-client.liquid.surf source code: https://github.com/Liquid-Surf/fedcm-demo/ In particular: https://github.com/Liquid-Surf/fedcm-demo/blob/main/packages/client/src/solid.js#L59C1-L143C1
I was able to make it work using handleRedirect option of session.login
async function inruptLoginWithFedcm(cssUrl, session) {
if (session.info.isLoggedIn) return;
await session.login({
oidcIssuer: cssUrl,
redirectUrl: window.location.href,
clientName: "Solid Demo App",
prompt: "consent",
// Use a custom handleRedirect to trigger FedCM and override the standard OIDC redirect
handleRedirect: async (url) => triggerFedcmLoginAndHandleResponse(url, session),
});
}
/**
* Triggers the FedCM login process using parameters from the redirect URL
* and completes the OIDC flow in the Inrupt session.
* @param {string} url - The redirect URL from the IdP (includes FedCM parameters).
* @param {object} session - The Inrupt session object.
*/
async function triggerFedcmLoginAndHandleResponse(url, session) {
try {
// Extract FedCM parameters (e.g., code_challenge, state) from the provided URL
const urlObj = new URL(url);
const params = Object.fromEntries(urlObj.searchParams.entries());
// Call the FedCM API to get a token
const identityRequest = {
providers: [
{
configURL: "any", // Triggers the registered IdP in the browser's FedCM
clientId: params.client_id,
registered: true,
params: {
code_challenge: params.code_challenge,
code_challenge_method: params.code_challenge_method,
state: params.state,
},
},
],
};
// Request credentials from the browser via FedCM
const fedcmResponse = await navigator.credentials.get({ identity: identityRequest });
// The returned token is an URL with code, iss and state params
await session.handleIncomingRedirect(fedcmResponse?.token);
// ... handle session login stuff here
} catch (error) {
// Handle errors here
}
}
}
@NSeydoux
Is there any concerns using handleRedirect that way ?
This looks really interesting, thanks for digging into it!
There is one thing that I'm puzzled about: the login promise should never resolve (see https://github.com/inrupt/solid-client-authn-js/blob/849b124c15c5f6482d4127c3f06595a3a9321f42/packages/browser/src/Session.ts#L217), because the browser navigation API is syncrhonous, and doesn't allow for clean asynchronous handling (as far as I remember). This means the handleRedirect parameter you pass in will be called, and the call to the FedCM API should happen, but I'm surprised your script goes beyonf this point, as session.login then still doesn't resolve.
Ha good catch, I didn't knew that..
However I think the behavior is expected, indeed session.login doesn't resolve but still update the session and doesn't block javascript, and then in another function call I'm able to trigger the session's fetch to get a resource.
I'm not sure I understand where the navigation API is called in this snippet, since I'm overriding the handleRedirect I'm bypassing the usual redirect ( actually I realized I remove the redirectUrl: window.location.href, line ) .
But if this goes again the design of the library, maybe instead of:
...
// inside triggerFedcmLoginAndHandleResponse
const fedcmResponse = await navigator.credentials.get({ identity: identityRequest });
// The returned token is an URL with code, iss and state params
await session.handleIncomingRedirect(fedcmResponse?.token);
...
We should do :
...
// inside triggerFedcmLoginAndHandleResponse
const fedcmResponse = await navigator.credentials.get({ identity: identityRequest });
window.location = fedcmResponse.token // we trigger redirect after the fedcm call
...
// later in the code in the root of the js script
// we handle the redirect url on page reload
(async () => {
await session.handleIncomingRedirect();
})();
Would that fit better with the library design ?