Outlook will rewrite email links to direct them towards https://*.safelinks.protection.outlook.com/ with the original url encoded as a query parameter, when a feature named “safe links” is enabled. In our testing, this appears to break magic links.
Steps to reproduce
Steps to reproduce the behavior:
- Create an email account on outlook.com
- Register a user with the @outlook.com (or @hotmail.com) email address
- Ask for a magic link to be delivered to the outlook email address
- Click the link
The “safe-links” feature can be turned off (see the last screenshot). If we do, then the magic links work as expected.
We have two hypothesis as to what might be causing this:
a) The passwordless code is somehow distorted as it is URL path encoded and decoded by the safe-links mechanism.
b) The safe-links mechanism makes a GET request to the magic link, thus using the code and making it invalid for future requests.
I can confirm I had the same problem with some customers. I asked them to remove the SafeLink feature while they tried to login to my website but it feel a bit “hacky” to ask customers to remove security features.
Yeah - especially when they work for Microsoft!
We’re had to send people from work emails to personal emails in the hope they don’t use Hotmail for personal use
Just created an account to share my solution to this problem when I encountered it. When the user clicks a link in an email, Outlook’s Safe Links accesses the url itself first using a HEAD request to determine if the website can be trusted, before then sending the user’s GET request. Our site uses django behind nginx as a reverse proxy, and uses magic links for new user authentication. The problem was that initial HEAD request caused the token contained in the URL to be checked, thus expiring it. The solution was to redirect any HEAD request to the activation url to the home page instead, or block any request to the activation url if it wasn’t specifically a GET request. We chose redirecting HEAD requests, as this allowed Outlook to verify that our site was safe, without expiring that token.
Hey P Martin - just wanted to say for the record and for anyone else - this was well diagnosed and I solved the problem with your approach which worked really well. I run a similar setup to you and essentially there are two ways to sort this
- Take any HEAD requests at the web server level requesting this URL and bounce them out somewhere safe rather than letting them through to the application code (faster, more secure, more complicated from an update perspective, might have unexpected side effects).
- Allow the request through as normal, but protect your view with the @require_safe method (which only allows HEAD / GET requests and then check request.method - if it’s HEAD - redirect the request somewhere you know is safe and will return a 200 (which is what I did and it worked).
Alternatively, you could also just block all HEAD requests from this endpoint (which I’d prefer to do) but initially I wouldn’t recommend it especially if you need a fast fix - because I’m not certain that blocking HEAD requests won’t trip something in the MS SafeLinks technology and give the user an ‘unsafe’ warning or similar.
Anyway yes, annoying bug but really appreciate the comment.
A fix for this was released in Ghost 3.41.0 back in January. If you’re still having problems on any later release then please let us know.