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 · 17 comments
Open

OAuth profile for FedCM #599

aaronpk opened this issue May 23, 2024 · 17 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.

@gioele-antoci
Copy link

Hello folks,
Joel from Shopify. I have chatted briefly with @samuelgoto about this but I wanted to ask this question to a larger audience.

From the top of the thread, when the oAuth journey reaches this point:

  1. Assertion endpoint validates stuff and returns an OAuth authorization code
  2. Browser JS sends the authorization code to its backend

The Oauth auth code passes over the front end channel. This might become a vulnerability in situations where the front end might be compromised (e.g. 3p scripts are present on the RP). Have we considered the risk of a malicious actor exfiltrating the auth code outside of the victim device? This is not a hypothetical scenario.

The attack itself would be similar to the one described here:

In this attack, the attacker intercepts the authorization code
returned from the authorization endpoint within a communication path
not protected by Transport Layer Security (TLS), such as inter-
application communication within the client's operating system.
Once the attacker has gained access to the authorization code, it can
use it to obtain the access token.

but the interception would have in the JS realm.

In the current spec status, is there any further mitigation other than shortening the TTL of the auth code? Is allowing server side redirects to a RP's /callback endpoint from the /assertion endpoint in order to exchanging the code via the BE channel a viable option?

@aaronpk
Copy link
Author

aaronpk commented Oct 18, 2024

Hi @gioele-antoci, there isn't anything different about this profile compared to a traditional OAuth redirect flow with regards to the authorization code. The mitigation for a stolen authorization code is to use PKCE, as I described in the profile.

The key that makes it work is the app server backend generates the PKCE code verifier, passing the hashed code challenge to the FedCM JS, and then the FedCM JS passes the authorization code to the server backend, which includes the PKCE code verifier when exchanging the authorization code. This ensures nothing can use the authorization code other than its backend.

@gioele-antoci
Copy link

  1. passing the hashed code challenge to the FedCM JS,
  2. and then the FedCM JS passes the authorization code to the server backend,

PKCE doesn't guarantee that the device who started the flow (the device a step #1) is the same device that passes the code to the BE (step #2), does it?

@wparad
Copy link

wparad commented Oct 18, 2024

It does, because only that device has access to the code verifier which used directly with the Authorization Service's token endpoint. There are no other resources between that device and the AS that would have access to that token.

@aaronpk
Copy link
Author

aaronpk commented Oct 18, 2024

PKCE doesn't guarantee that the device who started the flow (the device a step #1) is the same device that passes the code to the BE (step #2), does it?

That is in fact exactly what PKCE does.

@gioele-antoci
Copy link

gioele-antoci commented Oct 18, 2024

(would it be better to discuss this on a separate issue?)

because only that device has access to the code verifier which used directly with the Authorization Service's token endpoint.

if the code verifier is generated on the client-side (which it should - if I am not mistaken), then the attacker-controlled JS running on a compromised RP could get hold of that code and exfil it as well. What am I missing?

Let me add more context: a shopify merchant who owns merchant.com (RP) can install 3p JS apps in order to customize the shopping experience. if an app was malicious what prevents them to monkeypatch fetch (or anything else) and intercept every communication between the RP and the AS?

This is not a vulnerability in oauth nor in fedCM. This is situation where a compromised RP would be exposed to leak fedCM generated auth codes (unless I am wrong somewhere, which I might very well be): should fedCM be resilient and allow the auth code to be exchanged on the back end channel?

@aaronpk
Copy link
Author

aaronpk commented Oct 18, 2024

No, the code verifier should be generated on the server side, the server can send the hashed code challenge to the JS.

https://github.com/aaronpk/oauth-fedcm-profile?tab=readme-ov-file#navigatorcredentialsget

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

8 participants