next-auth: Signing in using magic link with Outlook email when SafeLinks enabled does not work

Describe the bug Signing in with an Outlook email which is linked to an account where the SafeLinks premium feature is enabled does not work.

Another authentication library seems to have the same issue: https://github.com/FusionAuth/fusionauth-issues/issues/629. When this Outlook feature is enabled, the magic link is encapsulated by a SafeLinks Outlook protection URL, e.g.:

https://eur02.safelinks.protection.outlook.com/?url=<magic-link-url>

From the issue in FusionAuth, it seems that the token gets invalidated because of some request that SafeLinks is being made beforehand. (Source: https://github.com/FusionAuth/fusionauth-issues/issues/629#issuecomment-629157742)

Steps to reproduce

  1. Sign in with Outlook email which has SafeLinks enabled
  2. Click on Sign link from email

Expected behavior User should be able to login

Screenshots or error logs None. The user gets redirected to /auth/sign-in?error=Verification

Additional context /

Feedback

  • Found the documentation helpful
  • Found documentation but was incomplete
  • Could not find relevant documentation
  • Found the example project helpful
  • Did not find the example project helpful

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 6
  • Comments: 20 (6 by maintainers)

Most upvoted comments

Just wanted to say for the record and for anyone else - I came into this issue (I run a platform using NGINX on the front, as a reverse proxy for a Django application, but the principles are the same for any stack) and here are some options for sorting it:

  • 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 (fast, secure, but more complicated from an update perspective, and might have unexpected side effects).
  • Alternatively allow the request through as normal, but protect your view to only allow HEAD / GET requests and then check if it’s HEAD - if it is, 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 but the solution for me was to handle the HEAD requests in the endpoint and pass them somewhere else, which doesn’t invalidate your tokens by completing the GET operation by accident.

Solved for me on both Outlook web client and desktop client with SafeLinks enabled.

Just adding in for anyone else coming in from google and experiencing this:

I’m seeing this issue with a lot of my corporate clients who rely on outlook. Organization protocol mandates SafeLinks, which screws them up, and invalidates their Login Via Email.

Would be great to see support for this merged into NextAuth out of the box!

I’ve done a bit more research, and it seems that Microsoft makes a HEAD request to the URL in the query params (our magic URL) before hand therefore “using up” the magic code and invalidating it for the user.

The fusion auth issue thread is pretty detailed / lots of discussion back and forth. A quick fix that seemed to work was to simply ignore HEAD requests in the invalidating magic code logic.

An Opencollective maintianer posted in that thread as well saying they’d had to work around a similar issue with outlook.com robots, and linked a PR through which they were able to get around the issue - https://github.com/opencollective/opencollective-frontend/pull/5476

Workaround Nr. 2 - the office365 admin can also apparently whitelist URLs which do not get this “safelink” treatment, so @dweemx, for now you cuold whitelist your application’s domain in order for magic link emails to work again in the short-term

As the GET and HEAD filtering doesn’t seem to work and Microsoft 365’s way of checking links is changing more often than an influencers mood and profile picture, isn’t there a more robust way of filtering the requests? Like the “Referer” header value… Does Microsoft 365 and other link checkers really have the site URL as “Referer” in the request? If not, then the easiest way might be to just ignore the POST/GET/HEAD method and all other values that probably will change, and concentrate on this one. Or is the “Referer” header value empty/invalid for the authentication request when the link is clicked? Also… If this issue is closed, was a solution found? Will it work for the next-auth 4.18.7+?

UPDATE: safe links no longer sends a HEAD request, instead it sends a GET with a very useful headers user-agent and x-native-host which contain OneOutlook somewhere in them.

example headers from the new outlook on windows 11:

'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47 OneOutlook/1.2023.927.100'
'x-native-host': 'OneOutlook/1.2023.927.100'

So I ignored every request with OneOutlook in the mentioned headers, and it worked!

example in Nuxt 3

// server/middlewares/bypass-safelink.ts
export default defineEventHandler(async (event) => {
  if (
    event.headers.get('user-agent')?.toLowerCase().includes('oneoutlook') ||
    event.headers.get('x-native-host')?.toLowerCase().includes('oneoutlook')
  ) {
    setResponseStatus(event, 200)
    return {}
  }
})

You might wanna check for the email’s specific callback endpoint, but I’m not sure if there’s anything else that sends those headers.