solid-client-authn-js icon indicating copy to clipboard operation
solid-client-authn-js copied to clipboard

FedCM support

Open elf-pavlik opened this issue 1 year ago • 6 comments

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.

elf-pavlik avatar Sep 26 '24 13:09 elf-pavlik

Hi @elf-pavlik , thanks for opening this. Support for FedCM would be an interesting addition to this library indeed.

NSeydoux avatar Oct 16 '24 08:10 NSeydoux

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.

michielbdejong avatar Nov 25 '24 15:11 michielbdejong

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

elf-pavlik avatar Nov 25 '24 20:11 elf-pavlik

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 ?

thhck avatar Mar 04 '25 11:03 thhck

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.

NSeydoux avatar Mar 06 '25 10:03 NSeydoux

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 ?

thhck avatar Mar 06 '25 13:03 thhck