We store the user’s session ID at the time they request the email be sent, and then validate it’s still the same session ID when the token (which is a parameter on the URL) is consumed.
If you’re using something like Mailchimp or SendGrid to send emails, this design also protects against a scenario where the email service is compromised by an attacker. The attacker will be able to see the emails, but will be unable to use the links to login.
Not too rare of a thing.
Or segments their browsing in different browsers/containers.
If it happens to be on the same browser that requested the login, it will be the same session anyway, and will work as you'd expect.
If it's a different device/browser, then (using your example) the user will see a message on their phone like "Login validated, please check the original device where you initiated login" and their desktop session will now be validated. Ideally the site uses polling or a push mechanism to proactively show the right page, but the lazy way is just to show a message like "Refresh this page after clicking the login link from your email".
The naive implementation is to just say "Hey, a valid token... Ok, you're now logged in", but this suffers from the issue mentioned in a parent comment:
>>> Links can be opened by the controller of the email even if they didn't make the original request. Links can also be opened by something scanning the email before the user sees them. Neither of these potentially malicious use-cases are covered by emauth.io.
>> We store the user’s session ID at the time they request the email be sent, and then validate it’s still the same session ID when the token (which is a parameter on the URL) is consumed.
> This breaks the use-case where the user is on a desktop computer but checks mail on their phone.
So what I'm saying is:
* Store the sessionId with the token
* When you handle the validation request, mark the stored sessionId as "logged in"
This handles the "click on validation from a different device" use case, while also preventing someone who's able to intercept your email from being able to log in themselves.
1. Request magic link on desktop. 2. Click magic link on mobile. 3. Become logged in on desktop.
gregmac mentioned (correctly) the things you’d want to do in terms of UI/UX if you want to allow for this flow.
A reply based design leaves you open to attacks because you have no way of having strong security guarantees/validation on the email reply. You would have to rely on validating that the email has valid DKIM, but DKIM won’t be available 100% of the time. And without DKIM the reply could be spoofed.
I've run into this a lot for things like when I want to log in to a public computer, such as to print something. Having a logged-in device with you makes this much quicker.
> A reply based design leaves you open to attacks because you have no way of having strong security guarantees/validation on the email reply. You would have to rely on validating that the email has valid DKIM, but DKIM won’t be available 100% of the time. And without DKIM the reply could be spoofed.
It's just as strong as using a link, because you can verify against the message ID of the original email. And has the advantage that it's not going to be triggered by your email client without action on your part. Plus you can use DKIM when it's available as a bonus security layer.
Again, this flow is still very much possible with a design that verifies session ID. Please re-read my last comment and gregmac’s comment carefully!
To be really clear, you can design it so that you:
(1) start the login process on a public computer, (2) click the link on a mobile device, and (3) end up logged in on the public computer. And you can do this while ensuring that the session ID is the same in steps #1 and #3.
My initial plan was to require the user to reply to the email, which solves the scanner problem and has the benefit of not opening a new tab. This comes at the cost of being an unfamiliar flow, and requiring an extra click. What are your thoughts on that?
I ended up starting with links since it worked for me (apparently gmail and fastmail don't pre-open links) and was simpler to implement for the prototype. Also it's less dependent on the speed of the user's email server.
Hi Anders — see my comment above for how do to this.