w3c-fedid / FedCM

A privacy preserving identity exchange Web API
https://w3c-fedid.github.io/FedCM/
Other
383 stars 73 forks source link

OAuth profile for FedCM #599

Open aaronpk opened 6 months ago

aaronpk commented 6 months ago

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:

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 commented 6 months ago

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 commented 6 months ago

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/continuation#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/continuation#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 support[^no-support-for-resource]. 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)

[^no-support-for-resource]: 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

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:

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.

anderspitman commented 6 months ago

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/continuation#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 commented 6 months ago

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 commented 6 months ago

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

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

bc-pi commented 6 months ago

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 commented 6 months ago

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

bc-pi commented 6 months ago
  • 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 commented 6 months ago

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 (https://github.com/fedidcg/FedCM/issues/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 commented 5 months ago

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

gioele-antoci commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago
  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 commented 1 month ago

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 commented 1 month ago

PKCE doesn't guarantee that the device who started the flow (the device a step https://github.com/w3c-fedid/FedCM/pull/1) is the same device that passes the code to the BE (step https://github.com/w3c-fedid/FedCM/pull/2), does it?

That is in fact exactly what PKCE does.

gioele-antoci commented 1 month ago

(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 commented 1 month ago

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