Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth profile for FedCM #599

Open
aaronpk opened this issue May 23, 2024 · 10 comments
Open

OAuth profile for FedCM #599

aaronpk opened this issue May 23, 2024 · 10 comments

Comments

@aaronpk
Copy link

aaronpk commented May 23, 2024

I went and wrote up a guide for how I would recommend using FedCM with OAuth and OpenID Connect. You can find it here:

https://github.com/aaronpk/oauth-fedcm-profile

It's written more as an implementation guide than a spec, but I'm planning on eventually formatting it more like a spec.

At a high level, the summary is:

  • Browser JS pings a backend server to tell it to start an OAuth flow (gives an opportunity to use PKCE, client authentication, and any extensions like PAR, RAR, etc)
  • Backend server returns a response to the JS (either PKCE code challenge, or PAR request URI)
  • Browser JS includes the PKCE code challenge or request URI in the FedCM API call
  • Browser does the FedCM stuff and makes a request to the assertion endpoint with the custom parameters
  • Assertion endpoint validates stuff and returns an OAuth authorization code
  • Browser JS sends the authorization code to its backend
  • Backend exchanges the authorization code for an access token / refresh token / ID token, including client authentication or any other extensions

NB: This works equally as well with a plain SPA as the OAuth client, skipping all the backend parts I described here, where the JS code itself is the OAuth client. It can still use extensions like PKCE/PAR/RAR/etc. I just suspect the backend version to be the more common deployment, especially since the Browser-Based Apps BCP recommends running a backend to manage OAuth tokens when you're building a SPA front-end.

@samuelgoto
Copy link
Collaborator

I just wanted to acknowledge and thank you for kicking this off @aaronpk !! I thought I was going to be able to read this carefully today and provide solid feedback, but other things got in the way. I skimmed through with joy but this deserves time. Nonetheless, I figure you should know that this is awesome and that we will be looking at this carefully over the next few weeks!!

@panva
Copy link

panva commented May 24, 2024

Updated 2024-06-20

I have gone ahead and did more mapping and experimentation

As far as OIDC/OAuth goes, a lot of the authorization_endpoint request pipeline grants itself to be re-used at the id_assertion_endpoint. RPs would use w3c-fedid/custom-requests#2 to pass the required authorization endpoint parameters (sans the ones that we don't need anymore, from the top of my head that's redirect_uri, response_mode, state, but could be more)

Note: Depending on the direction w3c-fedid/custom-requests#2 takes there is a new pipeline step necessary that transforms the FedCM request into oauth. It is also not possible at the moment to make use of all params we know and support1. This could be either www-urlencoded (i.e. what authorization endpoint POST binding already uses) parameter remapping to remove the prefix that current proposal has, or JSON body parsing where the fedcm API provided oauth params are in its own property as an JSON object (i.e. how we extract authorziation parameters from JAR)

The IdP processes the request as per FedCM's requirements (e.g. checking Sec-Fetch-Dest), has to support CORS and then responds with either a successful response as it would usually depending on the response type, passing the response as a www-urlencoded string to the "token" that FedCM recognizes (e.g. { "token": "code=foo" }, { "token": "code=foo&id_token=bar" }. Note that response_type=none must not be used with FedCM, otherwise no token is issued for the RP to consume.

Additional steps for FedCM:

  • Checking Sec-Fetch-Dest
  • Checking Origin to match one expected from the oauth client (e.g. based on its pre-registered redirect_uris origins or other fedcm-specific metadata)
  • CORS
  • Login Status API
  • check FedCM's client_id parameter value equals the oauth parameter client_id value
  • unless nonce is removed2 check that the fedcm nonce parameter is either empty or equal to the oidc nonce parameter
  • check FedCM's account_id parameter value equals the logged in end-user subject
  • Clear-Site-Data during logout if disconnecting the RP/IdP connection is wanted

The IdP can make use of FedCM's Continuation API should the authorization request pipeline deem it required.

Further mapping specific to OIDC would be that FedCM fields gets split and applied as the claims parameter which can be automatically granted given that disclosure_text_shown=true. If it's false then either pre-existing grant must exist or the Continuation API must be used to request the permissions.

The RP ends up with a standard OAuth Authorization Response in the credential that's parsed like so new URLSearchParams(credential.token), depending on the chosen response type it may contain an authorization code, an OIDC hybrid response with an ID Token and a code, or just an ID Token.

This mapping supports both public and confidential clients, allows RPs to re-use their redirect_uri callback handlers since the RP can take the query string representation of the authorization endpoint and append it to its regular redirect_uri. Clients may choose to use any authorization endpoint extension that's available at the IdP such as JAR, PAR, they may use PKCE, OIDC, any of the available response types and JARM should also be possible as well as DPoP at the token_endpoint.

The biggest hurdlea at the moment is FedCM-triggered IdP popup UI if the IdP cannot resolve the request straight away and has to use the continuation API to request permissions which may come as a second popup after sign-in (if button mode was used to also sign-in or sign-up), these popups come flying in on the screen, cannot have their optimal size defined by the IdP, and are generally a step down from a redirect based flow.

On the plus side this actually means the RP can drop-in FedCM whilst also supporting browsers without FeDCM support with their existing response handlers since it doesn't change how the authorization response looks.


Implementation notes:

It is possible to reuse almost the entire authorization request pipeline, add the FedCM bits when the request's route is the fedcm one, remove state and redirect_uri handling and otherwise treat the request as an authorization endpoint request with a fixed FedCM-specific response mode.

Endpoints acting as FedCM's accounts_endpoint, metadata endpoints, and optionally the disconnect endpoint seem to have no re-usable prior art and are at the implementer's discretion to handle as per FedCM's requirements, formats, etc.

Footnotes

  1. It is currently not supported to pass a single parameter multiple times so parameters such as resource cannot be used in that way. This should come as an option from FedCM in the params API

  2. https://github.com/fedidcg/FedCM/issues/616

@anderspitman
Copy link

This looks great!

I believe my LastLogin FedCM prototype essentially implements the OpenID Connect response_mode=id_token flow described at the bottom, except it's implied until w3c-fedid/custom-requests#2. I would gladly adopt the proposed interface.

@aaronpk you know infinitely more about OAuth security than I do. In the case of LastLogin, which is a pure IdP (no additional APIs or OAuth scope), is there any security benefit to doing the full authorization code flow as opposed to simply returning the ID token and letting the browser pass it to the RP backend? Obviously this requires full JWT validation as you mention.

@anderspitman
Copy link

One concern when trying to combine this with w3c-fedid/idp-registration#1. You could have some providers that implement only the authorization code flow, and some that only implement the id_token flow, and some that implement both. Would you have to have a separate type/variant for each combination in that case?

@aaronpk
Copy link
Author

aaronpk commented May 24, 2024

The main differences between OIDC auth code flow vs response_mode=id_token are:

  • response_mode=id_token means the ID token is sent to the browser before being sent back to the server, so there is potential information leakage depending on what's in the token (some IDPs put a ton of info like user groups etc)
  • response_mode=id_token means doing JWT validation after fetching the public key, as well as validating all the claims, including checking the nonce in the token to avoid injection attacks. Additionally, injection attacks are only prevented by the client after checking the nonce, at which point the injection has already happened, but the client stops it from being successful. In contrast, the auth code flow with PKCE prevents the injection from issuing any tokens in the first place, and is enforced by the IDP rather than the client.

Yes, a type/variant would need to define an actually interoperable profile of OAuth/OIDC in order to be useful.

@bc-pi
Copy link

bc-pi commented May 24, 2024

I am morally and contractually obligated to reiterate that this is a long way from an actual "specification" :)

But also say that this is super valuable work and thank @aaronpk again for doing it.

@aaronpk
Copy link
Author

aaronpk commented May 24, 2024

hahaha yes. I did call it a "guide" twice in the first post 😉

@bc-pi
Copy link

bc-pi commented May 24, 2024

  • response_mode=id_token means the ID token is sent to the browser before being sent back to the server, so there is potential information leakage depending on what's in the token (some IDPs put a ton of info like user groups etc)

ID tokens can be encrypted per spec but admittedly that's not widely supported.

@anderspitman
Copy link

ID tokens can be encrypted per spec but admittedly that's not widely supported.

That would require a relationship between the IdP and the RP (or at least a fetchable JWKS at the RP), which I'm trying to avoid in my implementation for privacy reasons (#595).

I realize this is a rather niche case, and currently not supported by the spec, but I'm hoping it ends up at least possible.

@panva
Copy link

panva commented Jun 20, 2024

I've updated my prior notes based on experimenting with the latest canary/available origin trials.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants