fedidcg / FedCM

A privacy preserving identity exchange Web API
https://fedidcg.github.io/FedCM
Other
357 stars 66 forks source link

Add optional `token_endpoint` to IdP config #584

Closed aaronpk closed 1 month ago

aaronpk commented 1 month ago

As I've been working on profiling this for OAuth/OIDC, in particular with IdP registration (#240), I encountered the need to include the OAuth token endpoint in the IdP config.

The flow is as follows:

The nice thing about doing it this way is it means we don't need to use a signed format for the token in the response from the FedCM API, since the RP never actually has to parse that value. It also means the JS code in the RP can send this value to its server, and the actual user data is transferred server-to-server, so it can be used with less validation too.

However, the problem arises in IdP registration mode, where the RP is not preconfigured with the IdP's information. The FedCM API returns the IdP's configURL in the response at the same time that it returns the authorization code, which is the first time the RP knows anything about which IdP was used. So we need to be able to go from the FedCM configURL to the token endpoint.

My solution was to extend my IdP config to include the IdP's token endpoint with a new parameter token_endpoint. An alternative would be to rely on the OAuth Server Metadata spec, which uses a .well-known path for the OAuth server metadata, however that still leaves the missing piece of going from the FedCM configURL to the OAuth issuer URL which is used for discovery, so I don't think that actually solves anything. Plus then you're more limited in use cases because of the .well-known requirement. So I like the simpler solution of including the token endpoint in the config.

npm1 commented 1 month ago

Oh interesting. Are you suggesting we add this optional parameter in the config URL and then perhaps include it directly from IdentityCredential so that the RP knows where to exchange the token they receive from FedCM right away? This seems reasonable to me, and fairly simple for the browser to do. The naming is a bit tricky though since we'd want to make sure it is clearly different from the ID assertion endpoint, which in turn used to be called the token endpoint

samuelgoto commented 1 month ago

Ha, that's an interesting proposal indeed.

As I've been working on profiling this for OAuth/OIDC However, the problem arises in IdP registration mode, where the RP is not preconfigured with the IdP's information.

Speaking of profiling OAuth/OIDC, have you considered an alternative design where you make the IdP return an agreed upon JSON object that contains both the code as well as the token_endpoint in the token response of the id_assertion_endpoint?

For example:

const credential = await navigator.credentials.get({
  identity: {
     providers: [{
       configURL: "any"
     }]
  }
});

const {code, endpoint} = JSON.parse(credential.token);

// RP now knows where to send the code to

Wouldn't that work?

The naming is a bit tricky though since we'd want to make sure it is clearly different from the ID assertion endpoint

Something I'm pondering is that the token_endpoint is an endpoint that the RP's server needs, but that it is never used by the browser, as opposed to every other endpoint. It also seems like an endpoint that it OAuth-specific, in that I'm guessing other protocols like SAML wouldn't use.

I guess, what I'm trying to get to, is that maybe we need something like #556 , but passing arbitrary data from the configURL to the RP (e.g. an extensible params property in the config file )?

aaronpk commented 1 month ago

The token endpoint is definitely OAuth-specific.

In my implementation I'm using a server backend, but if the OAuth client was in the browser, as with a full SPA, then the browser would need the token endpoint.

const {code, endpoint} = JSON.parse(credential.token);

I wouldn't want to return the endpoint directly, because that means my server has an endpoint that will accept an arbitrary string and a URL and POST the string to the URL, without being able to confirm that the URL is actually an OAuth token endpoint. So I like the added step of doing some discovery to find the token endpoint so that I know I'm at least about to post it to an OAuth server and not something else.

But you're right, I could return the OAuth server metadata URL in this response and that would be sufficient for both SPA and server apps.

aaronpk commented 1 month ago

Update: ok that worked fine. I don't love the JSON.parse there, it just feels wrong. But it does work, and I don't know if it's actually any better moving that parse into the browser instead, but that feels cleaner. You could think of it as allowing the IdP to return arbitrary JSON to the RP, then you wouldn't have to name the field token at all.

Closing this in favor of #578.