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

chore(docs): add tutorial for avoiding corporate email scanning HEAD reqs #3900

Merged
merged 5 commits into from
Mar 22, 2022

Conversation

ndom91
Copy link
Member

@ndom91 ndom91 commented Feb 9, 2022

Reasoning 💡

Saw someone else had this problem, and the "tutorial" / fix was relatively quick, so I decided to just write it up for the future.

See:

  1. NextAuth not working behind corporate proxy #2509
  2. Signing in using magic link with Outlook email when SafeLinks enabled does not work #1840

EDIT: There may need to be some extra checks before just returning a 200 all willy-nilly haha. What do yuo think Balazs?

Checklist 🧢

  • Documentation
  • Tests
  • Ready to be merged

Affected issues 🎟

@ndom91 ndom91 requested a review from balazsorban44 February 9, 2022 21:44
@vercel
Copy link

vercel bot commented Feb 9, 2022

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/nextauthjs/next-auth/4GoiB2CZaCsXhfZxurQEdymXeuCj
✅ Preview: Failed

[Deployment for 3a678fe failed]

Copy link
Member

@balazsorban44 balazsorban44 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The write-up was very good, I actually am very clear about this problem now!

Although I'm not sure in the case of HEAD how this would be an issue, as we only run NextAuth.js logic when the request is POST or GET.

body: `Error: Action ${action} with HTTP ${method} is not supported by NextAuth.js` as any,

I have to assume that some of those IT checks actually send a GET request instead, which could invalidate the token indeed.

I would rather add some kind of user-agent detection to the tutorial. Do we know SafeLink or any of those if they send some detectable info?

@ndom91
Copy link
Member Author

ndom91 commented Feb 10, 2022

The write-up was very good, I actually am very clear about this problem now!

Although I'm not sure in the case of HEAD how this would be an issue, as we only run NextAuth.js logic when the request is POST or GET.

body: `Error: Action ${action} with HTTP ${method} is not supported by NextAuth.js` as any,

I have to assume that some of those IT checks actually send a GET request instead, which could invalidate the token indeed.

I would rather add some kind of user-agent detection to the tutorial. Do we know SafeLink or any of those if they send some detectable info?

Yeah great idea. I think we can be pretty sure that at least office 365 / safelink use HEAD requests. Others I'm not sure.

What I can definitely picture is that they have a distinguishable user-agent or something like that for their bot. I just don't know how we'd get it 🤔

@ndom91
Copy link
Member Author

ndom91 commented Feb 12, 2022

Did some more research here, and it looks like opencollective were also not able to find any other distinguishing factors, other than that the bots user-agents showed they were using "old" versions of chrome (See: https://github.com/opencollective/opencollective-frontend/blob/2852c113685ffc8273419839c6012f11a7751fbb/lib/robots-detector.js).

But you're right, it does seem to be GET requests in most cases, not HEAD.

EDIT: Although there is a bunch of stuff we do in the handler before even checking if its POST/GET only.. Maybe it snuck by us and is happening somewhere in these lines before this check and that guy in #1840 (#1841) was on to something? Idk

const { options: userOptions, req } = params
setLogger(userOptions.logger, userOptions.debug)
const assertionResult = assertConfig(params)
if (typeof assertionResult === "string") {
logger.warn(assertionResult)
} else if (assertionResult instanceof Error) {
// Bail out early if there's an error in the user config
const { pages, theme } = userOptions
logger.error(assertionResult.code, assertionResult)
if (pages?.error) {
return {
redirect: `${pages.error}?error=Configuration`,
}
}
const render = renderPage({ theme })
return render.error({ error: "configuration" })
}
const { action, providerId, error, method = "GET" } = req
const { options, cookies } = await init({
userOptions,
action,
providerId,
host: req.host,
callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl,
csrfToken: req.body?.csrfToken,
cookies: req.cookies,
isPost: method === "POST",
})
const sessionStore = new SessionStore(
options.cookies.sessionToken,
req,
options.logger
)

EDIT 2: Nevermind, its really just asserting the config and init-ing the options, huh 🤔

EDIT 3: Opencollectives solution (see link above) was to present the user with a "js challenge" of sorts, requiring some sort of interaction (i.e. mousemove, touchstart, etc.) before continuing on and validating the email auth link. Thats kind of neat, doesn't require much code and would set us apart a bit I feel like. This just really does seem to be a niche issue and doesn't come up much :/

EDIT 4: Ghost, the blogging platform, seems to have worked around this by allowing tokens to be reused multiple times within a 10s window, i.e. if the user clicks on the link the outlook SafeLink bot will detonate it first, but then their page should load (i.e. the second visit) within 10s allowing them to also use the token validly. See: https://github.com/TryGhost/Ghost/pull/12519/files

@balazsorban44
Copy link
Member

Tha is for the thorough research! Some of these are nice tricks, but reusing the one-time token doesn't sound right to me. 😅

@ndom91
Copy link
Member Author

ndom91 commented Feb 12, 2022

Tha is for the thorough research! Some of these are nice tricks, but reusing the one-time token doesn't sound right to me. sweat_smile

Haha yeah so its just setting a timeout on the deletion of the one-time token from the db to 10s, so if the email link with that token happens to be hit again and looked up in the DB within 10s of the first time, it'll still be there. Kinda clever actually imo

@ndom91
Copy link
Member Author

ndom91 commented Mar 12, 2022

I think this one can just be merged. Any of the other work-arounds / potential changes we'd have to make are a little bigger, plus theres no "best-practice" out there in the community for dealing with this kind of thing yet so it'd require some more discussion about which route to even take, etc..

So I'd say having a basic tutorial like this is a decent middle-ground for now. What do you think? @balazsorban44

@ndom91 ndom91 merged commit b88a31e into main Mar 22, 2022
@ndom91 ndom91 deleted the ndom91/tutorial-corporate-email-scanning-head-req branch March 22, 2022 22:29
@rnbrady
Copy link

rnbrady commented Aug 30, 2022

@balazsorban44 wrote:

I would rather add some kind of user-agent detection to the tutorial. Do we know SafeLink or any of those if they send some detectable info?

@ndom91 wrote:

What I can definitely picture is that they have a distinguishable user-agent or something like that for their bot. I just don't know how we'd get it 🤔

I have a user who can't log in with the email provider so I ran some debug on Vercel. As soon as we send the verification email, we get two requests from their corporate firewall or proxy:

HEAD request with from user agent Barracuda Sentinel (EE)

GET request from Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)

Thought that might be useful to you guys.

@ndom91
Copy link
Member Author

ndom91 commented Sep 6, 2022

@rnbrady thanks for taking a look. This soundsn exactly like the Office 365 issue that sparked this whole issue/PR initially.

Were you able to get it to work, or the issue is still active for you? If so, I'd recommend opening a fresh issue and maybe pointing to this PR / existing issue(s).

Thanks!

@rnbrady
Copy link

rnbrady commented Sep 20, 2022

@ndom91 yes got it working as follows:

const authHandler = NextAuth(authOptions);

export default function auth(req: NextApiRequest, res: NextApiResponse) {
  // Workaround for known email scanners that send GET or HEAD requests which have
  // the effect of cancelling the one time token. We have seen:

  // HEAD request with user-agent: Barracude Sentinel (EE)
  // GET request with user-agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
  if (
    req.method === 'HEAD' ||
    /Mozilla.+MSIE.+Windows NT.+WOW64.+Trident.+SLCC2.+NET CLR/.test(
      req.headers['user-agent'],
    ) ||
    /lua-resty-http.+ngx_lua/.test(req.headers['user-agent'])
  ) {
    return res.status(200).send('Please visit the link from a browser.');
  }

  // GET request with double URL encoded param callbackUrl=https%253A%252F%252F
  if (req.url.includes('callbackUrl=https%253A%252F%252F'))
    return res
      .status(400)
      .send('Your proxy has mangled the callbackUrl parameter in the URL.');

  return authHandler(req, res);
}

@Andrew781123
Copy link

Andrew781123 commented Feb 18, 2023

Is this solution still valid? I came across the same issue but this solution doesn't solve it. I checked the logs and realise that there is noHEAD request, instead it is aGET request that triggered the magic link.
Screenshot 2023-02-18 at 22 15 33

mnphpexpert added a commit to mnphpexpert/next-auth that referenced this pull request Sep 2, 2024
…reqs (nextauthjs#3900)

* chore(docs): add tutorial for avoiding corporate email scanning HEAD requests breaking email invitations

* fix: move to internal guides section
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants