portier.github.io icon indicating copy to clipboard operation
portier.github.io copied to clipboard

Implement idP resolution using OpenID Connect Discovery

Open binki opened this issue 8 years ago • 23 comments

The concept of idPs resolved via email addresses (instead of OpenID discovery URIs) is my favorite feature of Persona. It made Persona much easier to use than traditional OpenID where a website either only permits the user to select from a list of trusted OpenID providers or the user has to paste in a “discovery URI”. Persona used the authority information inherent in the user’s identity (email), the domain name, as the starting point for idP discovery. Analogous to how an MTA looks up MX records, Persona checked for well-known DNS records to see if the mail provider defined an idP. If the mail provider did provide an idP, the user would get a much nicer login experience.

Based on how letsauth is right now implementing OpenID Connect and how it seems like a standard which will get wide adoption, perhaps letsauth could set a goal to define an idP lookup mechanism similar to Persona. Or maybe OpenID Connect already defines a way to do this…

At http://openid.net/connect/ , I see Discovery and Dynamic Registration. So perhaps Discovery defines a way to find what OpenID Connect provider should be used for an email address. Right in the spec is a description of how to form a WebFinger lookup for a typed email address. Given email [email protected], you would send a request to https://example.org/.well-known/webfinger?resource=acct%3auser%40example.org&rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer. If the idP implements this optional endpoint, the request should yield enough information to find the OpenID Connect idP for the user.

Of course, this is only the first step of a many step process and there are other points where failure could happen. I think maybe letsauth would have to use Dynamic Client Registration and keep track of its registrations with providers, etc. But at a glance it appears that decentralized idP lookup based on email address is already defined by a well-accepted standard and the standard which letsauth is using as a basis for its own protocol, nonetheless.

Of course, such a mechanism can’t replace letsauth. For email addresses for which OpenID Connect providers cannot be discovered, the email hash login would provide a baseline login experience. And a letsauth relier can be kept clean and simple if it can always delegate to letsauth rather than implementing the discovery itself.

binki avatar Aug 01 '16 01:08 binki

Hi @binki. I remember that we talked in an IRC meeting about this. I think that the WebFinger lookup was used in one model, maybe that was an idea of @skorokithakis at that time? Currently and as far as I could see, https://github.com/letsauth/ladaemon uses the discovery URL, see https://github.com/letsauth/ladaemon/blob/master/src/oidc.rs#L42. But I think that's where the webfinger lookup could be added.

onli avatar Aug 02 '16 07:08 onli

I ended up finding a discussion of something similar in the ML archives after the fact. Thanks for pointing to the spot in the source, maybe I’ll find time to play with the daemon, but I’m not sure yet.

binki avatar Aug 02 '16 13:08 binki

So, I’ve started trying to write my own dummy idP since I don’t know off-hand of any example implementations. So far I have a faked out webfinger: https://client.webfinger.net/lookup?resource=ohnobinki%40turbo.coffee . We’ll see if I get any futher… ;-).

binki avatar Aug 03 '16 05:08 binki

(Pinging @rfk to get his blog post up on OpenID Connect and the quality of its dynamic registration spec...)

callahad avatar Aug 04 '16 05:08 callahad

Actually, I somehow missed the attachment on one of @rfk's responses in that mailing list thread. That's a very good place to start.

at a glance it appears that decentralized idP lookup based on email address is already defined by a well-accepted standard and the standard which letsauth is using as a basis for its own protocol, nonetheless.

That's also my understanding. Without having actually implemented it, Webfinger does indeed look reasonable for discovery of a user's identity provider... but then we have to solve the problem of our daemon being able to talk to that provider without having been previously introduced and registered.

Similarly, what protocol does the provider speak? How do we make that easy enough for individuals with their own domain to become their own identity providers?

I don't have the headspace to tackle this right now, but I'd absolutely love it if someone (@binki, et al.) began exploring in this direction. It can be cleanly added to our list of auth strategies in the future, so it's not a blocker for v1.0, but we will need to solve this problem before we can claim to have lived up to our goal of empowering people to self-certify.

callahad avatar Aug 04 '16 05:08 callahad

So dynamic registration is one thing, but IIRC we wanted to try and keep the daemon stateless, right? And I assume most OIDC providers would not work well with trying to register the same callback URL a second time...

djc avatar Aug 04 '16 05:08 djc

IIRC we wanted to try and keep the daemon stateless, right?

Right. This is somewhere we likely need to develop a custom protocol, taking inspiration from Persona and OIDC Dynamic Registration and maintaining as much of OIDC as makes sense, without requiring true registration or long-term statefulness.

callahad avatar Aug 04 '16 05:08 callahad

Right now I’m slowly working through an implementation of the idP side of the specs as I have time. Hopefully the specs have some allowance for registration-free authentication, I think someone in that ML thread mentioned that implicit flow might allow that, but I haven’t gotten that far yet myself. Avoiding registration would also make the idP itself simpler.

One thing I did want to note here is a decision I’ve made about the WebFinger lookup itself. The OpenID Connect Discovery 1.0 says:

To find the Issuer for the given user input in the form of an e-mail address [email protected]

in this case, the acct: scheme [I‑D.ietf‑appsawg‑acct‑uri] is prepended to the Identifier.

However, the acct: spec and WebFinger spec describe acct: as intended for looking up non-email accounts. My interpretation of WebFinger’s spec is that: if you want to look up general accounts that might not have emails associated with them, use acct: (and you can end up with something unassociated with an email, like acct:[email protected]). But if you want to, you can use WebFinger to resolve an email address to an account by just using mailto:[email protected]. It is up to the WebFinger implementation whether or not it supports resolving mailto: and that would just be one of many requirements Portier would have for services to implement OPTIONAL features from the specs to support Portier ;-). I.e., Portier should do the idP lookup using mailto: rather than acct: to properly respect the specs.

I’ve implemented an example of that at https://client.webfinger.net/lookup?resource=mailto%3Aohnobinki%40turbo.coffee where mailto:[email protected] resolves to acct:[email protected]. Yay a tiny bit of progress? I have a long way to go… xD

binki avatar Aug 04 '16 14:08 binki

Hopefully the specs have some allowance for registration-free authentication, I think someone in that ML thread mentioned that implicit flow might allow that

Sadly, I'm pretty sure the existing implicit flow does not allow this, mostly because it would be a security problem - you need some way to tell what redirect_uri values are legit for each client.

I think we could invent a protocol that securely replaces "client registration" with "client discovery" along the lines of [1], but I'm not aware of any existing spec that avoids pre-registration of client credentials.

we wanted to try and keep the daemon stateless, right? And I assume most OIDC providers would not work well with trying to register the same callback URL a second time

If you're happy to keep a bit of state in a cache to reduce overall server load, you might actually get away with this. IIUC the dynamic registration spec is fine with you registering the same client details multiple times, and re-registering is the recommended way to e.g. replace your client secret in the event of a data breach.

[1] https://groups.google.com/d/msg/portier/kOc_SMY_9lI/DIuoD2HUBwAJ

rfk avatar Aug 04 '16 21:08 rfk

Even for the email link login to work you need state, right? Since it sounds like reregistering shouldn't be too big of a deal, caching the registration token for a few minutes at a time for the sake of staying with existing standards sounds like the way to go.

binki avatar Aug 04 '16 22:08 binki

Although TBH I'm not aware of any major OIDC IdP that actually implements the dynamic registration spec, so it may be a moot point.

rfk avatar Aug 04 '16 22:08 rfk

@binki You don't need state for the email login link, you can sign it using a secret so you just validate the signature when it comes back.

skorokithakis avatar Aug 04 '16 23:08 skorokithakis

The ladaemon however uses redis to store some state: https://github.com/portier/ladaemon/blob/master/src/store.rs. I think that's okay and would also be okay here. There is a difference between having some internal state used for making the authentication work and having state in the sense of users storing accounts and profil information. The line is where one ladeamon instance can't be swapped out with another without loosing stuff.

onli avatar Aug 05 '16 08:08 onli

Yeah, ladaemon has some state, but it's all short-lived. So if the database breaks down or whatever (or, if you want to setup your own instance), nothing is lost other than some limited performance penalty due to having to fetch more data in order to do the job.

djc avatar Aug 05 '16 08:08 djc

Has anyone looked at the proposals from ID4me? They want to solve discovery using a DNS TXT based solution. Might be interesting to add support for it on top of the WebFinger discovery.

mhofman avatar Sep 12 '18 01:09 mhofman

Hi @mhofman I hadn't looked at this yet. I see an incompatibility: We pretty much have baked into the project as a core assumption that the email address is the user identifier. I see it mentioned there as a possibility, but the main focus seems to be on URLs.

And the Identity agent concept seems to resemble the role Mozilla Personas main instance played in the end, the part that made it a centralized service by accident.

Is there a specific part of the spec at the discovery level that would be helpful for us?

onli avatar Sep 12 '18 17:09 onli

I believe they support any identifiers from the OpenID Connect spec, including email type "accounts". If Portier wants to focus on handling email addresses only, that's fine as it's a subset of supported identifiers.

Maybe I'm getting this wrong, but wouldn't the Portier broker be able to act as a Relying Party, discover ID4me authorities using the DNS TXT Discovery system on top of the existing WebFinger Discovery for other OpenID Connect systems.

The only thing I see as possibly being an issue is that ID4me basically mandates the usage of OpenID Connect Dynamic Client Registration, which means the broker would have to save the client_id / client_secret received the first time a new domain is encountered. This could probably be done in a sort dynamic configuration file? From what I gather, shouldn't registration also be used for discovered WebFinger domains.

mhofman avatar Sep 12 '18 18:09 mhofman

Maybe I'm getting this wrong, but wouldn't the Portier broker be able to act as a Relying Party, discover ID4me authorities using the DNS TXT Discovery system on top of the existing WebFinger Discovery for other OpenID Connect systems.

No, if I understand the spec correctly that would be possible. But what would we gain by that? Is there an email provider that has this set up?

For enabling an authentication flow without having to check mails for custom domains we already have the webfinger detection, which works fine as far as I'm aware. Is id4me popular enough that it would help many users that already have this set up for their email domain?

The only thing I see as possibly being an issue is that ID4me basically mandates the usage of OpenID Connect Dynamic Client Registration, which means the broker would have to save the client_id / client_secret received the first time a new domain is encountered.

Since the broker is supposed to be federated that might be a real issue though. So far we just don't do registration - it would only cause harm in our use case.

onli avatar Sep 12 '18 18:09 onli

No, if I understand the spec correctly that would be possible. But what would we gain by that? Is there an email provider that has this set up?

For enabling an authentication flow without having to check mails for custom domains we already have the webfinger detection, which works fine as far as I'm aware. Is id4me popular enough that it would help many users that already have this set up for their email domain?

WebFinger requires running an HTTP service that responds to the well-known URL on the anchor of the host part of the identifier. In the case of email identifiers that basically means the server at the apex of the domain! That is a major hindrance for most deployments.

The idea behind ID4me (from what I gather, I'm not associated to the effort, just interested in the technology) is that by being able to point to any auth provider by simply changing DNS records, anyone owning a domain name can use a 3rd party of their choosing to handle their identity, similar to how they can choose any provider for their domain emails. It could be a service of the domain registrar, a service of the email provider (e.g. Google Apps accounts?), or an independent identity service (e.g. Auth0).

Imagine if we required everyone needing emails on their domain to also run an based HTTP discovery service!

The only thing I see as possibly being an issue is that ID4me basically mandates the usage of OpenID Connect Dynamic Client Registration, which means the broker would have to save the client_id / client_secret received the first time a new domain is encountered.

Since the broker is supposed to be federated that might be a real issue though. So far we just don't do registration - it would only cause harm in our use case.

Google requires a client_id to be registered out of band. How is that provisioning done in the federated use case? Is it the same issue besides the dynamic nature of the registration?

mhofman avatar Sep 12 '18 19:09 mhofman

In the case of email identifiers that basically means the server at the apex of the domain! That is a major hindrance for most deployments.

Okay, that I get! I don't understand completely yet how the DNS entry has to be formed to show the IdP of the email address given. But I see now how the broad protocol could work. That could work nicely and fit well, as an alternative scheme for when the email domain doesn't want to run a webserver at root level.

Google requires a client_id to be registered out of band. How is that provisioning done in the federated use case? Is it the same issue besides the dynamic nature of the registration?

To activate sign in with google the broker has to be configured before, see the bottom https://github.com/portier/portier-broker#configuration, where a client_id is set. If it's only that it should be no issue, though I'm not sure that's actually still true when thinking about dynamic registration of multiple IdPs. Probably fine as well. Worst case it doesn't work with a different broker anymore, that's okay. I think I was mixing it up with the registration necessary in some OIDC authentication flows we considered for the flow between our RP and the portier broker. That's where we wanted to require no registration.

onli avatar Sep 12 '18 22:09 onli

Okay, that I get! I don't understand completely yet how the DNS entry has to be formed to show the IdP of the email address given. But I see now how the broad protocol could work. That could work nicely and fit well, as an alternative scheme for when the email domain doesn't want to run a webserver at root level.

The spec for the DNS TXT record is described in I-D.sanz-openid-dns-discovery. Basically it points the the host names to use for the identity authority and agent.

More informations about the interactions between parties are in their technical overview.

Google requires a client_id to be registered out of band. How is that provisioning done in the federated use case? Is it the same issue besides the dynamic nature of the registration?

To activate sign in with google the broker has to be configured before, see the bottom https://github.com/portier/portier-broker#configuration, where a client_id is set. If it's only that it should be no issue, though I'm not sure that's actually still true when thinking about dynamic registration of multiple IdPs. Probably fine as well. Worst case it doesn't work with a different broker anymore, that's okay. I think I was mixing it up with the registration necessary in some OIDC authentication flows we considered for the flow between our RP and the portier broker. That's where we wanted to require no registration.

Indeed, Portier broker would act as a Dynamic Registration client with the identity provider.

Just to make sure I understand, is it expected that a shared Portier broker handle Google authentications? The configuration seem to indicate that a single client_id for Google can be configured, which means the shared Portier broker acts as a sort of "independent" client to Google OpenID Connect authentication, hiding the real Relying Party's identity. Couldn't this be ripe for abuse?

In the case of a shared broker performing dynamic registration with identity providers, maybe the broker could retransmit the Relying Party's identity to avoid such issues. That would however mean storing the client secret based on the tuple (Relying Party server origin, identity provider), which can be argued is not simple configuration anymore, nor stateless. Of course if the goal is to have the Portier broker shield the RP client and the identity provider from each other, then using an client_id specific to the broker is right. The corresponding dynamic client id and secret can be stored in a pseudo configuration file, making things somewhat stateless. For some reason, I have a feeling not remembering the registration as suggested in an earlier comment may cause issues with some providers. At the very least it wouldn't be very nice to register new clients every time.

Again, all this isn't specific to ID4me, but to OpenID Connect Discovery and the Dynamic Registration supposedly necessary for it.

mhofman avatar Sep 13 '18 01:09 mhofman

When it comes to philosophical differences between Portier and ID4me, you're right, there might be some mismatch with what is considered the identity handle. The authority only provides a stable opaque handle identifying the authorized user. That handle can be specific to the Relying Party that asked to authenticate the user. The authority doesn't provide any claims itself, such as the email address tied to the identity. That would have to be requested from the agent (through the concept of OpenID distributed claims).

Technically multiple identifiers (emails, urls, etc.) can be linked to the same identity. The email identifier shouldn't be used as a stable handle. The reason is that if the domain expires and gets re-registered, the new owner would be able to steal the identity. Of course like the current Portier implementation, you can discover the email claim from the identity agent, and use that discovered email as the handle in Portier.

Personal motivation

I personally have a beef with most websites that insist on learning my primary email backing my Google account, which is why I'm interested in better authentication schemes. When I tested Portier with an email in the form of `[email protected]`, portier ended up showing I was logged in as `[email protected]`, which I then learned is by design.

What I'll probably end up doing is build a custom identity provider that returns an email specific to the relying party's origin, the same way as I do nowadays manually with my email aliases on my domain.

mhofman avatar Sep 13 '18 01:09 mhofman

When I tested Portier with an email in the form of [email protected], portier ended up showing I was logged in as [email protected], which I then learned is by design.

Well, design. It is by design, but it actually caused enough issues that we though about reverting it back, at least to only the normalization portier is doing. See https://github.com/portier/portier-broker/issues/165. Though that's more about the . in the address, less about the +, that portier would so far remove as well, with the current code. But it's the same issue. Like @stephank wrote:

I was personally thinking we should revert the change, and stick to our own, documented normalization only. That's basically lowercasing the email address (accounting for non-latin and international domain names.)

I assume you'd agree with that change.

I do as well, but it would mean quite some work for me making sure users of my services using portier don't get locked out of their account.

The email identifier shouldn't be used as a stable handle. The reason is that if the domain expires and gets re-registered, the new owner would be able to steal the identity.

No, the new owner just would be the new owner of the identity. That's unavoidable imho. The only way around that is adding meatspace authentication factors, 2-factor or passwords again, bound to the service or broker itself. Binding it to the broker would require state and destroy the federalization, so that's no option. A site wanting to change what is an identity - which is who controls the email address right now - would need to introduce 2FA on its own. If wanting to address that concern, Portier should be extended to support that in its auth flow - the RP providing the second factor, Portier doing the rest of the work.

The spec for the DNS TXT record is described in I-D.sanz-openid-dns-discovery. ...

Thanks for the links. So the DNS of the email domain would have an entry for the email address, the @ being converted to ._openidemail., pointing to the IdP. I doubt I can implement that, but I'd happily approve that PR.

onli avatar Sep 13 '18 09:09 onli