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

feat: Experimental FedCM compatibility #441

Merged
merged 48 commits into from
May 28, 2024
Merged

feat: Experimental FedCM compatibility #441

merged 48 commits into from
May 28, 2024

Conversation

sebadob
Copy link
Owner

@sebadob sebadob commented May 20, 2024

This PR will take care of implementing experimental support for FedCM behind a new config setting EXPERIMENTAL_FED_CM_ENABLE

@sebadob
Copy link
Owner Author

sebadob commented May 20, 2024

It is complete and should be working now.
However, for the implementation to make sense with OIDC, this issue should be resolved beforehand.

@sebadob sebadob marked this pull request as draft May 20, 2024 14:38
@sebadob
Copy link
Owner Author

sebadob commented May 22, 2024

The FedCM spec currently requires you to set auth cookie with SameSite=None. There is an open issue about this.
This PR will be on hold depending on the outcome.

@anderspitman
Copy link

@sebadob is this in a basically working state? If so what are you currently returning as a token? I suspect we're basically going to want to do the same thing with Rauth and obligator (LastLogin) and would love to start testing interoperability.

@sebadob
Copy link
Owner Author

sebadob commented May 25, 2024

I still can't see sebadob.dev in the list of accounts presented on lastlogin.net. No idea why.

Maybe because it has been registered in the past with these approved_clients returned and now it simply would not update the value, if you try to re-register? Could you try cleaning this value somehow?

However, I can see lastlogin.net on sebadob.dev.

That is most probably coming from the change to configUrl: "any" - perfect.

If you haven't run into this already, if you bail out of a login (ie click the X instead of selecting an account), the RP domain will get blacklisted. Go to chrome://settings/content/federatedIdentityApi to reset. The error to look for is "Third-party sign in was disabled in browser Site Settings."

Thanks! That will come in handy at some point. Never got into that situation so far.

I just triggered a new pipeline and will do some ephemeral client tests and then test with other clients like lastlogin.net and hopefully find out, why the login window does not appear. But I guess I am almost there. Thanks for your help!

@anderspitman
Copy link

Maybe because it has been registered in the past with these approved_clients returned and now it simply would not update the value, if you try to re-register? Could you try cleaning this value somehow?

Good point. That's likely it.

@anderspitman
Copy link

Is there a reason you need the additional client information beyond just the client id? I'm not using any of that in my implementation. Just shoving the client id into an oidc id token as the aud and sending it over fedcm. Very simple.

@sebadob
Copy link
Owner Author

sebadob commented May 25, 2024

Is there a reason you need the additional client information beyond just the client id? I'm not using any of that in my implementation. Just shoving the client id into an oidc id token as the aud and sending it over fedcm. Very simple.

Every value from EphemeralClientRequest that is not wrapped in an Option<_> is mandatory. This means in addition to the client_id, you need to provide at least:

  • redirect_uris
  • grant_types

For all missing values, Rauthy will just use defaults.

The redirect_uris are mandatory to make sure that you can only use this client from origins that actually belong to you or should be able to receive a token. If this would not be validated, any website in the internet would be able to get a token that is only meant for your client, which would be a huge security issue ofc.

The grant_types are mandatory to make sure that an initiating client only uses the flows that it is supposed to use. Most often this will just be authorization_code. I could make this an option as well and assume authorization_code by default, but what is mandatory and whats optional is actually derived from the official RFC.

For a simple setup, you just need to:

  • set the client_id to the exact same URI as the location where the JSON document is hosted
  • set the redirect_uris to for instance ["https://lastlogin.net/*"] to simply whitelist everything on that domain
  • set grant_types to ["authorization_code"]

Edit:

For the FedCM flow though it does not matter what grant_types you set, because they will be ignored anyway, since we can only return a single token (for now).

@sebadob
Copy link
Owner Author

sebadob commented May 25, 2024

@anderspitman Since I pushed a small bug for the accounts view, I will do another deploy soon. At the same time, I made the grant_types an option as well, so you will not need them once this is deployed. I had a second look and they are just there for completeness. Rauthy actually ignores this value anyway and only allows flows, that have been configured on Rauthy's side from the admin beforehand to keep ephemeral clients somewhat under control.

The pipeline will take ~40 minutes I guess.

@anderspitman
Copy link

anderspitman commented May 25, 2024

The redirect_uris are mandatory to make sure that you can only use this client from origins that actually belong to you or should be able to receive a token. If this would not be validated, any website in the internet would be able to get a token that is only meant for your client, which would be a huge security issue ofc.

For OIDC there's an alternative design which LastLogin uses and I learned from this excellent article. The idea is to simply require the client_id to be a URI and for it to be a strict string prefix of the redirect_uri, or even require them to be exactly equal. This is still secure, and is vital for self-hosted apps where the app might not have a public URI where it can host a client config. It's also nice and simple to implement.

For FedCM, you don't even need that much. The client_id can be completely opaque (preferably random). You never do an authorization code flow. Instead, you simply return an ID token directly through the FedCM ID assertion endpoint. Protocol-wise, this is similar to using the OIDC implicit flow.

I'm lobbying for them to add a way for IdPs to opt out of the browser sending the Origin header to the assertion endpoint. This should enable completely anonymous logins, which would be excellent for self-hosted apps.

@zicklag
Copy link

zicklag commented May 25, 2024

The idea is to simply require the client_id to be a URI and for it to be a strict string prefix of the redirect_uri, or even require them to be exactly equal.

I really like this idea! So far I've setup client JSON's for 5 different apps ( list here if interested ). If we could just made the client ID equal to the redirect URI, then that would mean the JSON is completely unnecessary, and it avoids an extra network fetch, and it avoids the need to cache the client.

The only app this wouldn't work for is the one that I had to set the signing algorithm explicitly because it didn't support ECDSA or whatever it was.

Even then, though, you could configure the signing algorithm with query string parameters in the client ID?

Or we could avoid the JSON by going JWT style ( loosely speaking ) and base64 encode the client JSON and use that as the client ID? I've wondered if that would work.

As long as it's secure, there's nothing that stops us from being creative here right? So far the 5 apps I've tried integrating with ephemeral clients have not had any related built-in functionality for it, it's all invisible to the app, which is really awesome.

Edit: I'm a little behind on the FedCM stuff, so I suppose my thoughts here are actually on ephemeral OIDC clients. I'll move this to chat, to not mix this thread up.

@sebadob
Copy link
Owner Author

sebadob commented May 26, 2024

The idea is to simply require the client_id to be a URI and for it to be a strict string prefix of the redirect_uri, or even require them to be exactly equal.

This is exactly what I am already doing.
The reason you need to specify them currently, is that you can use this json document for multiple clients this way without requiring you to host a new document for each app, which would be very annoying. But it's true that this param could be made optional and then just assume that the client_id must be an exact match. But you don't gain that much from it. In the end it is one line.

I'm w3c-fedid/FedCM#595 for them to add a way for IdPs to opt out of the browser sending the Origin header to the assertion endpoint. This should enable completely anonymous logins, which would be excellent for self-hosted apps.

I am not a big fan of this tbh. No origin checking may work for simple ephemeral clients, but all static clients would stop working immediately. All confidential clients for a boosted security as well.
It also makes it impossible for you to even create security audits (see where you are logged in, which apps do have access to your personal information, ...), makes refresh tokens impossible, and so on. If a login would not share any personal information about you, I would be fine with this, but this is not the case. You could literally log in to 1000 apps with this, and if you don't remember all of them, would never know where your personal data went, how long they have access to it, what they are doing with it, and so on.

At the end of the day, if you don't want your IdP to track you, use another one, its that simple. This is one of the reasons why I created Rauthy.

I really like this idea! So far I've setup client JSON's for 5 different apps ( list here if interested ). If we could just made the client ID equal to the redirect URI, then that would mean the JSON is completely unnecessary, and it avoids an extra network fetch, and it avoids the need to cache the client.

The only app this wouldn't work for is the one that I had to set the signing algorithm explicitly because it didn't support ECDSA or whatever it was.

This would work for super simple setups and "Hello World" examples, yes. But most often you want to configure stuff, which will not be possible without an additional fetch.
Regarding the 5 docs, you don't need to host all of them, if you don't like that, 2 would achieve the same outcome in this case. Just have one with the changed token algs, and all other ones could use the same document and you just need to add the redirect_uri accordingly.

Just one other thing - I would not use the ephemeral docs in prod, if you don't need them. Static, pre-registered clients will always be faster and more secure.

Even then, though, you could configure the signing algorithm with query string parameters in the client ID?

All possible, but totally worthless when every IdP is doing its own thing. The document above is coming from the official OIDC DCR extension and it is not even nearly complete in case of config params you can set. It contains only the ones Rauthy currently supports. If you would allow all of them as query params, you could easliy end up with a URI with thousands of chars.

Or we could avoid the JSON by going JWT style ( loosely speaking ) and base64 encode the client JSON and use that as the client ID? I've wondered if that would work.

Basically, you can put anything in there you like. Something that a lot of people forget is, that you want to try to keep your tokens small, because they are sent with every single request and can become a really huge overhead. For instance, just compare 2 tokens while one of them has been signed with RSA512 and the other one with EdDSA.
And then the JWT must be some standard, just the same as the JSON document and I would always prefer the JSON because of less overhead in the end.

As long as it's secure, there's nothing that stops us from being creative here right? So far the 5 apps I've tried integrating with ephemeral clients have not had any related built-in functionality for it, it's all invisible to the app, which is really awesome.

Yes that was the idea behind it.

@anderspitman
Copy link

anderspitman commented May 26, 2024

@sebadob rather than address all your points individually, I'll just say that I think our differences are mainly around how much power an IdP should have. You're interested in using more of the available features of OAuth2/OIDC (which is totally valid). I'm interested in an IdP that does as little as possible (asserting X user controlled Y identity at Z time). Yes there are tradeoffs. Always are. But I value privacy pretty highly so I'm willing to trade quite a bit for it.

One thing I will point out since you're concerned about where the user's data is being shared. In the case I'm talking about the surface area isn't increased, because the IdP is only sharing basic, relatively immutable data such as email address and name. Once those things are shared with an app, there's no unsharing them. Contrast this with something like an OAuth API for accessing online storage and that's a different story. You definitely need to have revocation there.

The problem is OIDC and OAuth often go together, when I think in many cases they could and should be separated. For example, currently if you log in to google you're logged in to google drive automatically, because google controls both your identity and your online storage. I envision a future where (privacy-centric) IdPs are kept simple and separate from data hosting. So I want to log in to both my online storage and my apps with something like LastLogin, which can have a tiny surface area and strong privacy guarantees, instead of one entity controlling both. Note that this is already the case for non-google drive online storage, except they usually use google as one of the default IdPs...

@anderspitman
Copy link

Hopefully this whole thing becomes moot because people end up using Weird/Rauthy/TakingNames.io to manage their identity on a domain they control (which I think is the logically conclusion of your suggestion to pick an IdP you trust). But I suspect there will be some intermediate steps between where we are and there, and I want to nudge protocols like FedCM in the direction of supporting more possibilities.

@sebadob
Copy link
Owner Author

sebadob commented May 27, 2024

OICD is just a superset of OAuth, so yes they do the same thing, except that OIDC can do more and usually has more information about the user.

One thing I will point out since you're concerned about where the user's data is being shared. In the case I'm talking about the surface area isn't increased, because the IdP is only sharing basic, relatively immutable data such as email address and name. Once those things are shared with an app, there's no unsharing them.

Absolutely true, which is why I would dislike anonymous logins even more. Currently I have configured the scope for all my Rauthy clients to only provide the data they actually need, not more not less. When you are allowing anonymous logins, you can only always provide a default dataset to the downstream app that always have to contain all data an app would need. But the problem is, that some apps don't need the full information about a user. When you have a default set, you are sending it anyway without additional user consent.
When I use an app that only needs my email and that's it, I do not want my IdP to also send full name, address, phone number, and so on. This is a way higher privacy concern than validating an origin.

@sebadob
Copy link
Owner Author

sebadob commented May 28, 2024

Merging this now as it is. The login window popup is the only issue left so far, but there are a few other things left that would need some more implementation on the Google side. As soon as they deploy updates, I will do these in additional PRs.

@sebadob sebadob marked this pull request as ready for review May 28, 2024 07:17
@sebadob sebadob merged commit 4689e54 into main May 28, 2024
@sebadob sebadob deleted the feat-fedcm branch May 28, 2024 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants