1. 44
  1. 22

    I think “No Third Party” in the case of magic links is wrong: the user’s e-mail provider is a trusted third party. Pwn the mail provider (or even something on the mail path), pwn the user.

    The same goes for the mail provider being down e.g. for maintenance.

    1. 5

      Oops, thanks for the correction. It should be third-party but decentralized. (Ignoring the case of running your own mail server or telephone network.)

      1. 3

        Even if it is your server, it might not be an encrypted email, or in your DC, or delivered to the right server first… easy enough to say you are ignoring those cases but they may not be that far from the norm.

        1. 1

          That is what I meant. Let me try to rephrase to be more clear:

          I will mark as third-party because the case of running your own tightly controlled mail server is rare and still depends on secure sending from the source server so there are likely third-parties involved.

        2. 2

          I don’t really see why you assume a login link is “leak proof” either - at its core a “login link” is just a one time password delivered out of band (via email).

          The server needs to (a) generate and send you the OTP and (b) have some way to verify that the OTP is correct, so any “leaking” issues that passwords have, can apply to email links just the same, and due to the “if someone can fuck it up they will” law of computer security, I wouldn’t want to assume that people implementing email links are treating them anywhere nearly as “securely” as we expect people to treat regular passwords.

          1. 1

            Yeah. I thought about this for a while. But I came to the conclusion that these hopefully short-lived OTPs didn’t really have the same impact as leaking a long-lived password.

            Really everything is a spectrum and I had to pick yes or no. For a well implemented system it seemed more like a yes. Although I agree it is easy to get wrong.

            1. 2

              Saying an OTP cannot leak means you’re assuming the the best case scenario for OTP handling, while simultaneously assuming the worst case scenario for password storage.

              If we consider “might” to mean “has the theoretical possibility, depending on implementation and other factors”, then realistically they both can potentially leak, and I think if you’re going to assume long lived password storage might leak, you have to assume OTP’s might leak too.

              Yes, an OTP should be short lived, but we don’t know that it’s implemented that way, and that it’s short lived (assuming it’s actually expunged after a short time) is almost certainly going to give people a false sense of security that a less secure implementation is “safe” (i.e. storing them in clear text).

              Yes an OTP shouldn’t be vulnerable to variants of replay attacks (i.e. a login link explicitly has to be a GET request, which means the OTP is potentially recorded in a lot of intermediate places), but we don’t know that it’s implemented that way.

              Yes both should be hashed strongly in a manner that nullifies the dangers if the hashes themselves were to leak, but we don’t know that it’s implemented that way.

        3. 4

          The same goes for password recovery emails, FWIW.

          1. 18

            I think that is the best argument in favour of email login links. “We are going to allow auth resets by email anyways, may as well make that the only way in.”

            1. 5

              Password recovery emails aren’t used anywhere nearly as often as a login email may be needed though, so downtime/maintenance issues are much less of a concern, as is non-instant delivery.

              Additionally, there’s a security aspect here: if your mailbox is compromised and someone uses it to retrieve a login link, you won’t necessarily know, ever.

              If your mailbox is compromised and someone uses it to reset your password, you’ll know the next time you try to login.

          2. 11

            Some other criteria this misses are:

            • Privilege separation: If one is forced to use SSO (e.g. a Google account) to access service X, anyone who gets access to the account can access service X. Users should have the option of separating services into as many privilege separation contexts as they like. Forcing users to use SSO prevents this. (For an instructive example, when Microsoft bought GitHub I was waiting to see if they were going to try and force GitHub accounts to be merged with Microsoft accounts — because it’s the sort of thing big tech is obsessed with doing when they acquire a company (all accounts must be unified!) — and something which would absolutely have caused me to flee GitHub.)
            • Non-tracking: Related to privilege separation, forced use of SSO means the identity to use service A can be correlated with the identity to use service B. This shouldn’t be a requirement, and users should be able to have arbitrarily many identities.

            The PAKE idea suffers from the issue that it treats the server as untrusted, but the server has to be trusted to send the pake attribute in the first place. This is also the issue suffered by all browser-based crypto, and why all browser-based crypto schemes are ultimately futile.

            You do mention having a different UI, but most users won’t notice the difference. I don’t think it would be possible to have browsers warn on absence of this attribute as it would impose requirements on existing websites as to how they store password hashes, etc. which may be infeasible to comply with. (e.g., what if authentication is done against an AD domain?)

            1. 2

              but the server has to be trusted to send the pake attribute in the first place.

              Not necessarily. The assumption is that you are using a password manager. This password manager would record that the password was using a PAKE and refuse to auto-fill it into anywhere that the site can access. So even if you were MITMed there would be two scenarios:

              1. The MITM sends a PAKE input field and can’t do anything with the password.
              2. The MITM strips the pake attribute and the password manager refuses to input the password.

              Of course the UX for this will be hard, but the browsers are already dealing with wrong certificates and other UX that has this level of scariness.

              1. 3

                The assumption is that you are using a password manager

                But why not just have a authentication manager in combination with TLS client auth or http auth?

                This would prevent from getting a password near the website front-end and with a good separation in the back-end also away from server implementation of the business logic.

                The big problem with TLS client auth (and http auth) is the beautiful UI in most browsers. In order to have PAKE implemented secure against server takeover and XSS the browser must have a similar UI. Require the use of a password manager would remove the ability to easy type the password on a complete new device. Also the browser need to handle the private key for the session. To complete the authentication there must some protocol based on the private key. The browser must also protect this key and prevent to sign arbitrary data. Otherwise a XSS-attack could use the key to sign another authentication session.

                So please keep the authentication as far away from the website as possible and make the UI for secure auth better. A better UI for TLS client auth would do this. I don’t know WebAuthn good enough to say, if it does this, but it looks like a good start.

            2. 5

              I would love to see the account recovery workflow in this comparison. Sure, it’s different per site, but getting rid of the “recovery link sent to email” is honestly kind of scary. Which means that in the end, passwords are merely a complication on top of the Magic Link flow, no?

              1. 5

                Recovery link is the exceptional case, login is the standard case, so should be significantly lower friction. I use sites less when they only have Magic Link flow.

                1. 3

                  Fair. I do find myself annoyed when magic links take a long time to arrive, presumably since I don’t use a popular email provider.

              2. 3

                The browser UX could potentially be improved a lot by going back to “Basic/Digest Authentication” style where combined with a built-in password manager the whole login flow would disappear if TLS client authentication for everything is not achievable (yet).

                1. 2

                  I think you made the case for WebAuthn just look better :) Regardless very nice analysis!

                  1. 5

                    Care to elaborate?

                    I think the major downsides of WebAuthn for me are:

                    1. A “regular user” can’t backup or transfer their credentials. Logging with a new device is a disaster.
                    2. IIUC no mutual authentication support. (Although maybe this can be added as I would like to do with passwords).
                    1. 2

                      A “regular user” can’t backup or transfer their credentials. Logging with a new device is a disaster.

                      That’s not currently easy but it’s not a limitation of WebAuthn. Logging in with a new device for most WebAuthn services I’ve used is to log in with one of my existing devices and press the ‘allow this new device’ button. You could make that completely transparent with some QR-code magic: If you’re logged in, show a QR code contains a single-use URL that adds a new device to the logged-in account. If you’re not logged in, show a QR code that contains a single-use URL that requires you to log in with an already-authorised device and signs you in.

                      The flow that you want is not to transfer your credentials because your credentials should always live in a tamper-proof hardware token (TPM, U2F device, enclave on the phone, whatever), it’s to transfer authorisation.

                      The big problem with WebAuthn is in the opposite direction: revocation. If my phone is stolen, hopefully its credential store is protected by a biometrics and something that rate limits (with exponential back-off) attempts to unlock the device with spoofed biometrics, so it’s probably fine, but there’s no good way (yet) of saying ‘revoke my phone’s WebAuthn credentials for every single service that I can sign on with’.

                      IIUC no mutual authentication support. (Although maybe this can be added as I would like to do with passwords).

                      As I understand the WebAuthn flow, this is not quite true. With a password, if I provide my password to a phishing server, it is compromised. With WebAuthn, there is a challenge-response protocol. A phishing site must intercept a valid challenge from the site that it is trying to attack and ask me to sign it. My signature will use a key derived from a local secret and the domain that I am talking to. If I get a challenge that originated from example.com but that was forwarded by evilxample.com that is trying to phish me, then I will sign it with a key that is SomeKDF(evilxample.com, myDeviceSecret). The signature will then not match what example.com expects. The MITM attempt then fails. I don’t get a direct failure indication, but the phishing site can’t then show me anything that I expect to see because it has failed to log in to the site that it is trying to MITM my connection to.

                      Note that I already get the authentication in the opposite direction from TLS, the problem is not at the protocol level, it’s at the UI level: users can’t tell the difference between example.com and example.phish.com. WebAuthn should at least mean that if they log into example.phish.com, the intercepted credentials can’t be used to log into example.com.

                      1. 5

                        The flow that you want is not to transfer your credentials because your credentials should always live in a tamper-proof hardware token (TPM, U2F device, enclave on the phone, whatever), it’s to transfer authorisation.

                        The problem here is that it is a disaster to transfer all of these authorizations when you get a new device. And if you miss one before getting rid of your old devices you are now locked out of your accounts. (Unless there is backup authorization options of course.) This is why I only use 2FA on a tiny number of services. Managing which keys are enrolled in which services is a disaster even for a single-digit number of services. I can’t imagine a word where I had to do this for every login.

                        That is why the password is nice. I can sync my whole list of passwords or even write them down without needing to involve every service.

                        … With WebAuthn, there is a challenge-response protocol …

                        I think you are confusing phishing resistance with mutual authentication.

                        Note that I already get the authentication in the opposite direction from TLS

                        Yes, I agree that for most use cases one direction of auth provided by TLS and one direction of auth provided by whatever else is sufficient. However it is always nice not to need to rely on the PKI. Things are getting better with certificate transparency and pinning but the sad fact is still that for any site you haven’t visited before there are dozens of third-parties who have the authority to validate that connection. Reducing that set to only the site I want to take to is an absolutely huge improvement.

                        1. 1

                          This is why I only use 2FA on a tiny number of services. Managing which keys are enrolled in which services is a disaster even for a single-digit number of services. I can’t imagine a word where I had to do this for every login.

                          Because of this, I wrote my own TOTP app that syncs the 2FA with all my devices. Which is totally wrong if you ask any security expert – the TOTP should be one way.

                          Personally it’s an acceptable risk, because I ensure I don’t sync the passwords via the same mechanisms as the TOTP. If one sync is hacked, it doesn’t affect both parts of the 2FA.

                          1. 4

                            This is why I hate 2FA with TOTP, it loads most the security obligations to the user (while having the server store the shared secret in clear text).

                            1. 2

                              I do the same. All of the OTP secrets go into my password manager.

                              Most sites don’t even support multiple OTP devices and there is no way I’m getting locked out of sites because my phone was broken or stolen.

                        2. 1

                          What do you mean by mutual authentication? Is this not another word for “unphishable”?

                          1. 3

                            They are related and I think mutual authentication probably implies unphishable but not the other way around.

                            For example if I use a password manager I am basically unphisable because it doesn’t fill the password into the wrong site. But mutual authentication provides stronger validation of the site.

                            For example if you are using WebAuthn I think a phishing site can pretend to log you in without knowing anything about you, then ask for sensitive info. For example you go to g00gle.com, tap your authenticator and then it asks you for sensitive information. It seems legit because you just logged in. With mutual authentication unless the site actually knows who you are and has per-exchanged credentials the browser will fail to perform the authentication and let you know. No website can pretend that you have an account.

                            Of course getting UX that makes this obvious to the average user is definitely not easy. Maybe we need a new padlock that shows that you are using a password-authenticated connection. And of course we need to make sure that they can’t make a “signup” form look like a “login” form and get that lock for a new account.

                            1. 1

                              With this explanation I believe The no on mutual authentication for TLS client auth is wrong. TLS client auth sends a signed record of the Key exchange till the auth message. So yes the client don’t directly checks the server cert, but it isn’t a problem because the auth message can only used to auth this exact session. So even if you trick the user to auth against g00gle.com with the key for google.com the attacker can’t do anything with it.

                              1. 1

                                I’m not sure, because the browser prompts you in its sacred area, and also if the domain is new it must go through an enrollment phase which looks distinctly different.

                                1. 3

                                  I tested and this doesn’t appear to be the case. The following code seems to result in a “login” dialog on any site when clicking the button. At lest for my simple stateless security key (Yubikey Neo 4 IIRC). The request does actually fail but at least on Firefox there is no user-visible indication of this. Maybe this UX could be added and at least the site would need to know a valid key for the user.

                                  <button id=b>Click me</button>
                                  <script>
                                  function rand(len) {
                                    let r = new Uint8Array(len);
                                    window.crypto.getRandomValues(r);
                                    return r;
                                  }
                                  
                                  b.onclick = function() {
                                    navigator.credentials.get({
                                      publicKey: {
                                        challenge: rand(32),
                                  	  allowCredentials: [{
                                          type: "public-key",
                                          id: rand(64),
                                        }],
                                      },
                                    })
                                  };
                                  </script>
                                  

                                  Live demo: https://cdpn.io/kevincox-ca/fullpage/rNpvGNy

                                  1. 3

                                    The request does actually fail but at least on Firefox there is no user-visible indication of this

                                    I was able to verify this claim, indeed Firefox doesn’t show any indication of it failing. However if I test this in Chromium it does give a good error! Namely

                                    Try a different security key. You’re using a security key that’s not registered with this website

                                    Tracking this down, I figured out why Firefox doesn’t do the same. It’s caused by Firefox only implementing CTAP version 1 (aka U2F), which can’t detect this. CTAP 2 however does support that, and Chromium implements that. You can see how it works by searching for CTAP2_ERR_NO_CREDENTIALS in the CTAP2 specification. See this bugzilla issue for the status of CTAP2 in Firefox. This also prevents username-less login from working in Firefox at the moment.

                                    Given that, I think your mutual authentication criteria on webauthn can be dismissed. Though we might have to wait a bit longer for Firefox to catch up, but the specification is already stable.

                                    1. 1

                                      It still isn’t clear to me exactly how unguessable these keys are expected to be. I guess these should be basically completely random since they are public keys of a certificate pair but it also depends if they are site-specific (I think they are supposed to be) otherwise you do a 2-part phishing where you get the key ID from one site and use it on a different one.

                                      But it sounds like you are right. I can probably mark WebAuthn as mutual authentication.

                                      1. 2

                                        Guessing public keys isn’t possible, but it also doesn’t matter, because servers don’t have a way to check whether a public key is valid. If you mean credential IDs instead, that’s also unguessable, check the linked specification. And yeah public keys and credential ID’s are site-specific. Webauthn takes great care to make it hard to track users between multiple websites. Check out this section of the specification which goes into detail about this concern.

                                        If you have other questions or concerns about webauthn, I’d love to help. I spent a while with this specification (I worked on a server implementation) so I know it pretty well by now.

                                        1. 2

                                          Thanks for the info! I did find the spec really hard to navigate and maybe skimmed it faster than I should have. I’ve updated the post to say that WebAuthn provides mutual authentication. It maybe isn’t quite as strong as a TLS connection witk PAKE but I think it is close enough that for a simple comparison like in this post saying ✅ is more correct than ❌ and not really worth a footnote for the details.

                          2. 1

                            [removed accidental duplicate]

                          3. 1

                            <input type=password pake></input>

                            Input is a self-closing tag. Ignoring that, it should be <input type=pake value="http://example.com/endpoint">. PAKE is a distinct type from a password field. If you want to hide the password field if a browser supports PAKE, that’s what JS is good at. PAKE needs an endpoint field so you know where to do the exchange, and you can keep the keys separate by endpoint, so it’s not possible to send a key to the wrong server or endpoint.

                            1. 2

                              Input is a self-closing tag.

                              Oops, will fix. I still hate the idea of self closing tags, so confusing.

                              <input type=pake …>

                              I talk about this a bit at the end. I thought this might be a good approach as it allows graceful upgrade. Old browsers will just send as a regular form (with clear-text password) and new browsers will do the PAKE. I honestly didn’t think about it too much I’m sure there are dozens of use cases to consider before deciding to go one way or the other.

                              PAKE needs an endpoint field so you know where to do the exchange

                              I was thinking that the action of the form could potentially be used for this.

                              that’s what JS is good at

                              I would really like it if this could work nicely with no JS if at all possible.


                              Really this was a simple example to show the idea. I would expect that it isn’t the best solution. It is definitely interesting to see the places that would require changes.

                              1. 3

                                I was thinking that the action of the form could potentially be used for this.

                                The form action would then need to know how to parse regular inputs and how to do PAKE and when to switch over. I think it’s too much for one endpoint to have to manage. It also doesn’t allow for people who want to do the login form as JSON over AJAX (I know you want no JS, but we also shouldn’t force no JS).