God I hate those. Just give me two input fields, one for email/username, one for the password. On the same fricking page, please. Everything else just adds friction!
A similar pet peeve: services that implement 2FA by emailing or texting the code instead of simply letting you store the TOTP secret in your app of choice.
Just yesterday this failed when I wanted to login to a service and their email and texting infrastructure was extremely slow to send things.
Annoyingly TOTP is generally considered insecure because of relaying and cloning. Banks in Europe for instance are legally barred from using TOTP (does not pass the minimum requirements of the payments directive).
Yeah my Swedish bank uses the BankID system (as all banks here do), but it's not too much of a problem from a user perspective because it's an app on my phone and I don't have to wait for an email to (fail to) arrive.
There are similar systems, though, which are pretty much the same.
e.g., Photo TAN, which Commerzbank and Deutsche Bank use, where you scan a matrix code with either an app or device to activate it, and then for each transaction you get another such code, scan that, the device will generate a one-time token, which you then input.
Under the hood it's basically just TOTP/HOTP, but instead of using a timestamp or counter, it's using the actual transaction data that's presented in the per-transaction matrix code.
e.g., Photo TAN, which Commerzbank and Deutsche Bank use, where you scan a matrix code with either an app or device to activate it, and then for each transaction you get another such code, scan that, the device will generate a one-time token, which you then input.
I am not a Photo TAN user but pretty sure that the secret key for Photo TAN is literally your bank card smart card. Eg: you need physical possession of the chip that your account is linked to. As such it cannot be cloned. Photo TAN i believe also transfers information onto the device that lets you see what you are signing.
This gets really interesting when you're traveling abroad, the service you need to use only supports SMS 2FA, and your phone is on a local SIM with your home SIM disabled.
It often means you have to pay for a "day pass" with your home carrier just to receive a SMS OTP.
I run a small service that people only log into once or twice a year. We don't do magic link but it is very tempting, because lots of users forget their password.
Meanwhile they have constant access to their email, and their email conforms to whatever corporate security rules they have in place.
The last time I griped about this my replies blew up with SAML people going on about SSO things I'm too small business to understand. Apparently there's a good reason for all of us to have our toes repeatedly stubbed this way :shrug:
You can also check whether an email is connected to an SSO provider while the user is entering the email, so by the time they press "<tab>" you can already show either a password field or a "Login with SSO" button.
Anyone claiming SSO to be the reason to have email and password be separate pages is just bullshitting.
I have plenty of services that integrate with my Authentik instance for SSO, and many of them have email + password field on the same page, with the option for SSO/IdP providers below it.
Roll that out at scale and now you have tons of users putting their SSO credentials into the third-partys login form and complaining it doesn't work. Or getting confused because they don't have a password (because they use other auth methods). Not everyone making different trade-offs then you is immediately "bullshitting".
Yeah, it's very hard to create enjoyable SSO flows that cover user error without splitting user email and password into two steps. I'm not happy about that either, but it's a really hard problem to solve.
You can say that again. Multipage logins are so annoying and frustrating. Why do people even complicate this? Multipage logins can't possibly be simpler to implement than a couple of input fields.
I agree they’re annoying. But they’re largely a result of organisations having a variety of SSO rules. As a vendor can be tricky to work out without knowing which identity the user will be asserting. Not an excuse - just how we ended up here.
The only benefit of a magic link is proving you have control over an email address when logging in, which many more services want than is strictly necessary, because mailing lists are profitable or something
The only benefit of a magic link is proving you have control over an email address when logging in.
Which from a customer support perspective is very important. Generally magic links are maybe not optimal user experience on actual sign in, but it resolve the entire problem of people forgetting their passwords which happens all the time.
To avoid this pitfall, the link should do nothing but mark the code as “verified” and instruct the user to return to their original browser tab, which should auto-refresh every few seconds to check whether the code is verified, and if so, log the user in.
Wouldn't this make the user vulnerable to phishing? An impersonating site can forward the login request to the legitimate site, and once the user clicks the magic link in their email, the attacker's code gets verified.
Somewhat alarmingly, some of these links were already claimed before I could click on them. Then I realized, some programs issue GET requests to render link previews.
Trial and error often helps discover problems, but the HTTP specification defines that safe methods, such as GET, do not change the state of anything. If an HTTP request changes something, use an unsafe method, such as POST.
People often say "the great thing about standards is there are so many to choose from." Sometimes there is only one standard to choose from, yet implementors still manage to choose something else.
I do remember this from many years ago. I think it was Chrome that implemented link prefetching and many people were complaining that they were logged out of different services (IIRC it was wordpress).
Another downside is you're training your users to click on links, which in the age of scams/malware/phising attempts is a bad practice.
Anyone that accesses your email can use the code (if it's still valid) . Send a code instead, it's only valid for a particualr session, even if someone intercepts the code they can't necessary use it.
Dear god YES. This 100%. Just today, I got an e-mail which was ostensibly from GitHub, about a signin from a "new" location in a country I've never visited and if I could please review it. I was on guard but the mail looked quite legit, except that the links were all rewritten by sendgrid (or it seemed like it was). So now I couldn't even see where the links went to.
I decided to log in to GitHub by just visiting the website manually and reviewing the sessions there. Of course, there was no trace of the session in question...
I got that one today too! It was very convincing, but upon closer inspection the sender was "alerts@ridemajestic.com", who I'm guessing got their sendgrid accounts compromised.
Also got hit by it today! Almost got me since I was on mobile and the sender email isn't immediately visible. Definitely going to be checking twice going forward 😅
This reminds me of how my old bank, which we will say is named foobank, used foobank2.com in some of its URLs for reasons I don't understand. And the credit card management was done through foobank.mycreditcardaccess.com. It's like they were trying to make it impossible to build up a map of what URLs are trustworthy.
The database should store a hash of the secret code, not the code itself
That makes little sense and makes a lot of flows in practice tricky. In particular you want to reissue the same code if triggered again in a short period of time to avoid bad user interactions. Or at least keep the old code alive. Since they have to be short lived, there is absolutely no reason why you can't store them in plain text.
Rather than reissue the same code in a short time period, why not recognize that the already issued code is still valid and skip issuing the email, text, etc.?
Decreases the entropy if there are multiple valid ones. If you don’t resend email / text you run into issues with people who accidentally deleted them or had networking issues.
It's a real shame that all these improvements make the service so much more stateful. Now you need to do database lookups on every login before even doing the lookups for what your program needs to display... It's a shame to make authenticaion so stateful.
I don't think that's systemically true, since I believe that's part of the reason JWT was invented. If I sign a tuple of your user-id and some expiry with my RSA key then every server which has access to the public key can confirm that credential is valid without any change in the application's state. One may want to use caching/database/session/valkey/whatever but I don't think it's obligatory
Ironically, with that setup then unauthentication becomes stateful, if one needs to invalidate a token before that aforementioned expiry. I did actually wonder if that's what you meant but thought I'd take my chances with the comment anyway
You could argue that JWT logins are stateful, but the state is stored client-side. Used wisely, it's a great scalability enabler. For logouts, one way is to use a denylist, which is likely small enough (explicit logouts are rare, usually you just let the JWT time out) to keep synced in your service's memory.
I am not a big fan of passkeys, but if you’re not going to even bother having the option to use a username+password to authenticate people (cough shopify store pages cough), I’d rather use those than magic links.
I was never a fan of any "online" 2FA workfows, given I'm always expecting network interactions to fail. Better be able to log in with a code generated on my device than waiting for an e-mail that might never arrive. This could be the reason I started using TOTP somewhat early (Google says I'm using since 2012, not sure if I used it somewhere else before that), and while I didn't trust hardware keys that might be lost without backups, I liked passkeys ever since they became usable.
Still, to my surprise, it was only in recent years that I discovered that some people, both with and without technical backgrounds, simply reset their password every time they want to log in to a service they do it infrequently. My impression is that magic links were invented to solve this exact use case. As long as they are implemented in a safe-enough way (only valid for a single login, with a short expiration time, etc.), they are certainly more user-friendly than abusing the password resetting option.
I am generally in favor of people trying to understand and implement things themselves and not just defaulting to pulling in a library, but that doesn't mean that you should just try to invent everything from first principle without reading, learning, studying other solutions and so on. There are so many issues with what the article author is doing. Making the magic link validate a completely separate browser session makes this vulnerable to phishing, which makes a username/password combination just as secure as this. But in itself is not the real problem, the real problem is just thinking that you can test and reason your way into a secure system without learning about all of the possible ways that a secure solution can fall apart that have been discovered and resolved over time. This is not something you can solve via superior intellect alone.
Unlike most folks in the thread, I like magic links. They prevent me from having to track yet another password and so forth. In this sense, login is only a click away and I find that convenient.
Yes to all the "best practices" in this article, especially the extra click (which should be a POST) but it's important to consider the user base. There are a lot of people out there who can only barely manage their email password, so anything more complicated (like a password manager, even a built in browser one) may not work for those users. I helped run a multi-year basic income study where we used magic links because it was the lowest barrier our users in aggregate could manage.
diktomat | 11 hours ago
God I hate those. Just give me two input fields, one for email/username, one for the password. On the same fricking page, please. Everything else just adds friction!
Sharparam | 9 hours ago
A similar pet peeve: services that implement 2FA by emailing or texting the code instead of simply letting you store the TOTP secret in your app of choice.
Just yesterday this failed when I wanted to login to a service and their email and texting infrastructure was extremely slow to send things.
benoliver999 | 7 hours ago
All my banks do this, and don't even have the option of TOTP. So annoying.
mitsuhiko | 6 hours ago
Annoyingly TOTP is generally considered insecure because of relaying and cloning. Banks in Europe for instance are legally barred from using TOTP (does not pass the minimum requirements of the payments directive).
Sharparam | 6 hours ago
Yeah my Swedish bank uses the BankID system (as all banks here do), but it's not too much of a problem from a user perspective because it's an app on my phone and I don't have to wait for an email to (fail to) arrive.
justJanne | 6 hours ago
There are similar systems, though, which are pretty much the same.
e.g., Photo TAN, which Commerzbank and Deutsche Bank use, where you scan a matrix code with either an app or device to activate it, and then for each transaction you get another such code, scan that, the device will generate a one-time token, which you then input.
Under the hood it's basically just TOTP/HOTP, but instead of using a timestamp or counter, it's using the actual transaction data that's presented in the per-transaction matrix code.
mitsuhiko | 5 hours ago
I am not a Photo TAN user but pretty sure that the secret key for Photo TAN is literally your bank card smart card. Eg: you need physical possession of the chip that your account is linked to. As such it cannot be cloned. Photo TAN i believe also transfers information onto the device that lets you see what you are signing.
adriano | an hour ago
This gets really interesting when you're traveling abroad, the service you need to use only supports SMS 2FA, and your phone is on a local SIM with your home SIM disabled.
It often means you have to pay for a "day pass" with your home carrier just to receive a SMS OTP.
fedemp | 35 minutes ago
e.g. Payoneer.
benoliver999 | 7 hours ago
I run a small service that people only log into once or twice a year. We don't do magic link but it is very tempting, because lots of users forget their password.
Meanwhile they have constant access to their email, and their email conforms to whatever corporate security rules they have in place.
rsalmond | 7 hours ago
The last time I griped about this my replies blew up with SAML people going on about SSO things I'm too small business to understand. Apparently there's a good reason for all of us to have our toes repeatedly stubbed this way :shrug:
justJanne | 6 hours ago
You can also check whether an email is connected to an SSO provider while the user is entering the email, so by the time they press "<tab>" you can already show either a password field or a "Login with SSO" button.
Sharparam | 6 hours ago
Anyone claiming SSO to be the reason to have email and password be separate pages is just bullshitting.
I have plenty of services that integrate with my Authentik instance for SSO, and many of them have email + password field on the same page, with the option for SSO/IdP providers below it.
sknebel | 6 hours ago
Roll that out at scale and now you have tons of users putting their SSO credentials into the third-partys login form and complaining it doesn't work. Or getting confused because they don't have a password (because they use other auth methods). Not everyone making different trade-offs then you is immediately "bullshitting".
mitsuhiko | 6 hours ago
Yeah, it's very hard to create enjoyable SSO flows that cover user error without splitting user email and password into two steps. I'm not happy about that either, but it's a really hard problem to solve.
mwcampbell | 2 hours ago
Having SSO and magic link as the two options avoids that problem.
spenc | an hour ago
I trust my password manager and don’t trust my SSO provider. Please don’t get rid of username+password
matheusmoreira | 6 hours ago
You can say that again. Multipage logins are so annoying and frustrating. Why do people even complicate this? Multipage logins can't possibly be simpler to implement than a couple of input fields.
jonathannen | 3 hours ago
I agree they’re annoying. But they’re largely a result of organisations having a variety of SSO rules. As a vendor can be tricky to work out without knowing which identity the user will be asserting. Not an excuse - just how we ended up here.
polywolf | 7 hours ago
The only benefit of a magic link is proving you have control over an email address when logging in, which many more services want than is strictly necessary, because mailing lists are profitable or something
mitsuhiko | 7 hours ago
Which from a customer support perspective is very important. Generally magic links are maybe not optimal user experience on actual sign in, but it resolve the entire problem of people forgetting their passwords which happens all the time.
zie | 3 hours ago
Because of MFA/SSO/etc requirements, one is basically forced to move the password box to a different page to have any hope of UX sanity.
fleebee | 8 hours ago
Wouldn't this make the user vulnerable to phishing? An impersonating site can forward the login request to the legitimate site, and once the user clicks the magic link in their email, the attacker's code gets verified.
singpolyma | 4 hours ago
It also falls apart if the user has already closed the original tab.
benoliver999 | 7 hours ago
Yeah surely the 'original tab' is the one the attacker is going to be using
tomhukins | 7 hours ago
Trial and error often helps discover problems, but the HTTP specification defines that safe methods, such as GET, do not change the state of anything. If an HTTP request changes something, use an unsafe method, such as POST.
cjs | 7 hours ago
Ooh boy is it fun when you find out that your edge proxy server has an error condition where they will retry a POST request.
tomhukins | 6 hours ago
People often say "the great thing about standards is there are so many to choose from." Sometimes there is only one standard to choose from, yet implementors still manage to choose something else.
fedemp | 33 minutes ago
I do remember this from many years ago. I think it was Chrome that implemented link prefetching and many people were complaining that they were logged out of different services (IIRC it was wordpress).
Jackevansevo | 7 hours ago
Another downside is you're training your users to click on links, which in the age of scams/malware/phising attempts is a bad practice.
Anyone that accesses your email can use the code (if it's still valid) . Send a code instead, it's only valid for a particualr session, even if someone intercepts the code they can't necessary use it.
sjamaan | 7 hours ago
Dear god YES. This 100%. Just today, I got an e-mail which was ostensibly from GitHub, about a signin from a "new" location in a country I've never visited and if I could please review it. I was on guard but the mail looked quite legit, except that the links were all rewritten by sendgrid (or it seemed like it was). So now I couldn't even see where the links went to.
I decided to log in to GitHub by just visiting the website manually and reviewing the sessions there. Of course, there was no trace of the session in question...
jfloren | 4 hours ago
I got that one today too! It was very convincing, but upon closer inspection the sender was "alerts@ridemajestic.com", who I'm guessing got their sendgrid accounts compromised.
msfjarvis | 4 hours ago
Also got hit by it today! Almost got me since I was on mobile and the sender email isn't immediately visible. Definitely going to be checking twice going forward 😅
benoliver999 | 3 hours ago
Plus the code is handy if you are trying to access the service from a different device to the one your email is on (very common for my users)
quasi_qua_quasi | 2 hours ago
This reminds me of how my old bank, which we will say is named foobank, used foobank2.com in some of its URLs for reasons I don't understand. And the credit card management was done through foobank.mycreditcardaccess.com. It's like they were trying to make it impossible to build up a map of what URLs are trustworthy.
dsr | an hour ago
Every email message sent by an noreply@ address is a lost opportunity to engage with a customer.
A remarkable number of businesses are built on the core premise of not engaging with customers.
mitsuhiko | 7 hours ago
That makes little sense and makes a lot of flows in practice tricky. In particular you want to reissue the same code if triggered again in a short period of time to avoid bad user interactions. Or at least keep the old code alive. Since they have to be short lived, there is absolutely no reason why you can't store them in plain text.
Riolku | 3 hours ago
Also, secret codes are usually low entropy, so hashing them seems useless when it can be brute forced if leaked within seconds.
Sanity | 5 hours ago
Yeah, I was wondering about that too. What would the reasoning be here?
andrus | 3 hours ago
I think the idea is that “it’s a secret, and secrets shouldn’t be stored in plaintext”
andrus | 3 hours ago
Rather than reissue the same code in a short time period, why not recognize that the already issued code is still valid and skip issuing the email, text, etc.?
mitsuhiko | 2 hours ago
Decreases the entropy if there are multiple valid ones. If you don’t resend email / text you run into issues with people who accidentally deleted them or had networking issues.
Vaelatern | 12 hours ago
It's a real shame that all these improvements make the service so much more stateful. Now you need to do database lookups on every login before even doing the lookups for what your program needs to display... It's a shame to make authenticaion so stateful.
hauleth | 6 hours ago
But authentication is stateful process anyway… Due to the nature of web and authentication it must be stateful.
mdaniel | 4 hours ago
I don't think that's systemically true, since I believe that's part of the reason JWT was invented. If I sign a tuple of your user-id and some expiry with my RSA key then every server which has access to the public key can confirm that credential is valid without any change in the application's state. One may want to use caching/database/session/valkey/whatever but I don't think it's obligatory
Ironically, with that setup then unauthentication becomes stateful, if one needs to invalidate a token before that aforementioned expiry. I did actually wonder if that's what you meant but thought I'd take my chances with the comment anyway
moltonel | 2 hours ago
You could argue that JWT logins are stateful, but the state is stored client-side. Used wisely, it's a great scalability enabler. For logouts, one way is to use a denylist, which is likely small enough (explicit logouts are rare, usually you just let the JWT time out) to keep synced in your service's memory.
cjs | 7 hours ago
I am not a big fan of passkeys, but if you’re not going to even bother having the option to use a username+password to authenticate people (cough shopify store pages cough), I’d rather use those than magic links.
myhro | 5 hours ago
I was never a fan of any "online" 2FA workfows, given I'm always expecting network interactions to fail. Better be able to log in with a code generated on my device than waiting for an e-mail that might never arrive. This could be the reason I started using TOTP somewhat early (Google says I'm using since 2012, not sure if I used it somewhere else before that), and while I didn't trust hardware keys that might be lost without backups, I liked passkeys ever since they became usable.
Still, to my surprise, it was only in recent years that I discovered that some people, both with and without technical backgrounds, simply reset their password every time they want to log in to a service they do it infrequently. My impression is that magic links were invented to solve this exact use case. As long as they are implemented in a safe-enough way (only valid for a single login, with a short expiration time, etc.), they are certainly more user-friendly than abusing the password resetting option.
krig | 3 hours ago
I am generally in favor of people trying to understand and implement things themselves and not just defaulting to pulling in a library, but that doesn't mean that you should just try to invent everything from first principle without reading, learning, studying other solutions and so on. There are so many issues with what the article author is doing. Making the magic link validate a completely separate browser session makes this vulnerable to phishing, which makes a username/password combination just as secure as this. But in itself is not the real problem, the real problem is just thinking that you can test and reason your way into a secure system without learning about all of the possible ways that a secure solution can fall apart that have been discovered and resolved over time. This is not something you can solve via superior intellect alone.
yonkeltron | 3 hours ago
Unlike most folks in the thread, I like magic links. They prevent me from having to track yet another password and so forth. In this sense, login is only a click away and I find that convenient.
Sharparam | 46 minutes ago
You can do it like Slack does: Provide an option to login with magic links, but still allow traditional logins.
yonkeltron | 13 minutes ago
Terrific point!
jc00ke | an hour ago
Yes to all the "best practices" in this article, especially the extra click (which should be a
POST) but it's important to consider the user base. There are a lot of people out there who can only barely manage their email password, so anything more complicated (like a password manager, even a built in browser one) may not work for those users. I helped run a multi-year basic income study where we used magic links because it was the lowest barrier our users in aggregate could manage.hongminhee | 12 hours ago
I think we can probably add the security tag?
fanf | 5 hours ago
Use the |suggest| button to propose tag changes so that the comments are not cluttered with metadiscussion.
hongminhee | 5 hours ago
Thanks!
timvisee | 5 hours ago