Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signing in using magic link with Outlook email when SafeLinks enabled does not work #1840

Closed
1 of 5 tasks
dweemx opened this issue Apr 24, 2021 · 29 comments
Closed
1 of 5 tasks
Labels
enhancement New feature or request providers stale Did not receive any activity for 60 days

Comments

@dweemx
Copy link

dweemx commented Apr 24, 2021

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: FusionAuth/fusionauth-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: FusionAuth/fusionauth-issues#629 (comment))

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
@dweemx dweemx added the bug Something isn't working label Apr 24, 2021
@dweemx dweemx mentioned this issue Apr 24, 2021
3 tasks
@balazsorban44
Copy link
Member

balazsorban44 commented May 3, 2021

I am a bit confused and have no knowledge of what SafeLinks is. Could you point to some documentation and/or try to explain your problem a bit better?

This is certainly not a bug, as it doesn't reproduce any undesirable or documented action.

@balazsorban44 balazsorban44 added enhancement New feature or request providers and removed bug Something isn't working labels May 3, 2021
@ndom91
Copy link
Member

ndom91 commented May 17, 2021

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 - opencollective/opencollective-frontend#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

@dweemx
Copy link
Author

dweemx commented May 18, 2021

Indeed @ndom91
We followed the suggestion from the discussion i.e.: ignoring the HEAD request.

@Raggok
Copy link

Raggok commented May 22, 2021

any workaround that we can implement ???

@dweemx
Copy link
Author

dweemx commented May 23, 2021

@Raggok, one workaround is available here #1841

@Raggok
Copy link

Raggok commented May 23, 2021

how can i use this workaround on my production code? thx!

@dweemx
Copy link
Author

dweemx commented May 23, 2021

You can either use this branch https://github.com/dweemx/next-auth/tree/fix/prod%2Fmagic-link-outlook-safe-links or you'd have to fork NextAuth and patch it in custom branch and install NextAuth using that particular branch

@balazsorban44
Copy link
Member

balazsorban44 commented May 23, 2021

@dweemx as mentioned, your suggestion in #1841 cannot possibly work, as I explain in #1841 (review). I am guessing you have added some other changes as well. Right?

@ndom91
Copy link
Member

ndom91 commented May 23, 2021

@Raggok you can also whitelist domains in the office365 portal apparently, haven't tried it myself but I've read its possible - let me see if I can find the docs again.

EDIT: Check out this document about setting up safe links policies: https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/safe-links?view=o365-worldwide#do-not-rewrite-the-following-urls-lists-in-safe-links-policies

So you could whitelist the domain of your application and then outlook won't "check" the URL and trigger the magic link 1-time-use URL and invalidate it for the user.

@thmsrmbld
Copy link

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.

@stale
Copy link

stale bot commented Jul 25, 2021

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep it open. (Read more at #912) Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Jul 25, 2021
@stale
Copy link

stale bot commented Aug 1, 2021

Hi there! It looks like this issue hasn't had any activity for a while. To keep things tidy, I am going to close this issue for now. If you think your issue is still relevant, just leave a comment and I will reopen it. (Read more at #912) Thanks!

@stale stale bot closed this as completed Aug 1, 2021
@rdoyle99
Copy link

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!

@balazsorban44
Copy link
Member

Doesn't seem like a general enough use case.

See a proposed solution here: #1840 (comment)

See https://next-auth.js.org/configuration/initialization#advanced-initialization for advanced use cases like this. If someone creates a tutorial/example repo specific to this problem, I'm happy to link to it from the docs under https://next-auth.js.org/tutorials#advanced for example.

@cymen
Copy link

cymen commented Jul 16, 2022

I ran into this problem and ended up adding a /api/ok endpoint that just returns HTTP 200 with "ok" as the body (and doesn't require authentication). Then I added this rewrite rule in the nginx configuration:

    if ($request_method = HEAD) {
      rewrite ^/api/auth/callback/email.* /api/ok last;
    }

You of course might have to do something else and there might be some way to do this without adding the /api/ok route but I wanted that anyway.

@mbaraa
Copy link

mbaraa commented Oct 4, 2023

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.

@happyfire
Copy link

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.

As I just tested, there is no user-agent and x-native-host headers in safelink request now. And there are only 'x-real-ip', 'x-forwarded-for', 'connection' and 'hsot' in headers.

@mbaraa
Copy link

mbaraa commented Oct 27, 2023

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.

As I just tested, there is no user-agent and x-native-host headers in safelink request now. And there are only 'x-real-ip', 'x-forwarded-for', 'connection' and 'hsot' in headers.

are you sure that you tested it with the new outlook? (the one with the hexagonal icon, and the word "new" on it)

@MagnusAycox
Copy link

MagnusAycox commented Mar 21, 2024

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+?

@JonParton
Copy link
Contributor

I've also encountered this today and found no way around it ... There are no obvious indications when it is outlook / Defender detonating the links (As they want to appear as human as possible so malicious actors can't act good for them either!) so the only option I can seem to think of is to make the tokens multi use for a short time period to get around this ... which I'm sure is terrible practice.

Any suggestions of other ways around this welcome! ...

p.s. for office 365 domains you control you could of course disable this link checking for your specific domain...., but you can't on unknown client's outlook instances!

@FilipMasar
Copy link

@balazsorban44 We also just run into this issue. I think it affects a lot of users. I would say almost every corporate users using outlook or maybe also other email providers.

Do you think there might be some way around this on a library level?

@ndom91
Copy link
Member

ndom91 commented May 29, 2024

@FilipMasar

Yeah so the general idea is that outlook/other email providers scan all the URLs in the email to check for malware, etc. and by doing that and making a request to the use-verification-token-up endpoint of Auth.js, they "use up" the verification token so that it can't be used again. So when the user goes to click on the link in the Email, it'll fail because the token is already used.

So more specifically, some workarounds / fixes could be to (1) allow verification tokens to be used twice, this isn't a good idea for many other reasons. So (2) would be to filter out the requests from outlook/other email clients and not count those against verification token requests.

As you can see in this thread, many people have found and tried various filters for Outlook requests, but (1) outlook seems to change this every once in a while and (2) you don't want to block legitimate traffic at the same time.

I dont think anyone on the team has the time to dig into this further at the moment, but if you or anyone else come across a good filter that only targets and blocks Outlook / email client traffic without blocking legitimate traffic, we can definitely take a closer look and potentially implement it 🙏

@FilipMasar
Copy link

FilipMasar commented May 29, 2024

@ndom91 thanks for the feedback.

We did some research and ended up with following solution. We create our custom magic link which redirects users to "welcome" page and includes original magic link as a query parameter. On this welcome page user has to click button to continue. Button just navigates to original magic link.

Hope this can help others as well. I believe this is the best approach to this.

We found out this option here https://supabase.com/docs/guides/auth/auth-email-templates#email-prefetching

@ndom91
Copy link
Member

ndom91 commented May 30, 2024

@FilipMasar ah yeah this is a great idea too, nice one

@frederickmannings
Copy link

frederickmannings commented Jun 6, 2024

Same issue here.

Thanks @FilipMasar for the suggestion. Will implement a similar solution and inform whether it worked for us!

@FilipMasar
Copy link

@frederickmannings actually we needed do do one more thing and that is add recaptcha. Because it looks like bots can also click on buttons. Here it is described more MicahParks/magiclinksdev#3

I don't actually understand why is this happening. If we would be sending emails with unsubscribe link, then will users be automatically unsubscribed because of this outlook safe link functionality?
We also noticed that users with outlook are getting our emails with ~10mins delay which is terrible UX. It is probably because outlook is first checking if the email is fine. Does anyone know if we can do something about it? Is this the case with every email that ends up in outlook?

@pip8786
Copy link

pip8786 commented Jun 20, 2024

I don't actually understand why is this happening. If we would be sending emails with unsubscribe link, then will users be automatically unsubscribed because of this outlook safe link functionality?

I don't know about your other questions but from my tests the token is only consumed when you click on the link. Links in the email get verified when you click them, either by embedding them into a MS URL or verifying before it goes to the browser.

@frederickmannings
Copy link

I don't actually understand why is this happening. If we would be sending emails with unsubscribe link, then will users be automatically unsubscribed because of this outlook safe link functionality?

I don't know about your other questions but from my tests the token is only consumed when you click on the link. Links in the email get verified when you click them, either by embedding them into a MS URL or verifying before it goes to the browser.

That's not what my experience suggests. It seem to me that outlook is following the links, as when the user eventually gets them (embedded or not), the token has been consumed.

@FilipMasar, FWIW adding an additional page with a submit button and reCAPTCHA did the trick for me also 👍🏼

@davidlky
Copy link

davidlky commented Jan 9, 2025

We also noticed that users with outlook are getting our emails with ~10mins delay which is terrible UX. It is probably because outlook is first checking if the email is fine. Does anyone know if we can do som

Did you figure out why this is happening? @FilipMasar ? getting the same delay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request providers stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests