next-auth: AzureADProvider does not work with default (common) endpoint

Provider type

Azure Active Directory

Environment

“next” : N/A “react”: N/A “@auth/core”: “0.12.0” “@auth/sveltekit”: “0.3.1”

Reproduction URL

N/A (it is a basic setup)

Describe the issue

I am using simple setup for AzureADProvider. My goal is to allow login with any valid Microsoft identity (I have app registration setup, etc). I am am not providing tenantId will default to common

[auth][error][SignInError]: Read more at https://errors.authjs.dev#signinerror
[auth][cause]: OperationProcessingError: "response" body "issuer" does not match "expectedIssuer"
    at Module.processDiscoveryResponse (file:///<...skipped...>/node_modules/oauth4webapi/build/index.js:232:15)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async getAuthorizationUrl (file:///<...skipped...>/node_modules/@auth/sveltekit/node_modules/@auth/core/lib/oauth/authorization-url.js:19:20)
    at async Module.signin (file:///<...skipped...>/node_modules/@auth/sveltekit/node_modules/@auth/core/lib/routes/signin.js:14:20)
    at async AuthInternal (file:///<...skipped...>/node_modules/@auth/sveltekit/node_modules/@auth/core/lib/index.js:115:36)
    at async Proxy.Auth (file:///<...skipped...>/node_modules/@auth/sveltekit/node_modules/@auth/core/index.js:100:30)
    at async Module.respond (/<...skipped...>/node_modules/@sveltejs/kit/src/runtime/server/respond.js:274:20)
    at async file:///<...skipped...>/node_modules/@sveltejs/kit/src/exports/vite/dev/index.js:505:22
[auth][details]: {
  "provider": "azure-ad"
}

The root cause is the way Microsoft implemented oidc.

How to reproduce

  1. Configure AzureADProvider provider following the documentation:
export const auth: Handle = SvelteKitAuth({
	trustHost: true,
	secret: AUTH_SECRET,
	providers: [
		AzureADProvider({
			clientId: AZURE_CLIENT_ID,
			clientSecret: AZURE_CLIENT_SECRET,
		}),
	],
});
  1. Try to login with any Microsoft identity

Expected behavior

User is redirected to the Microsoth login page and login works…

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Reactions: 9
  • Comments: 25 (1 by maintainers)

Most upvoted comments

@cbdabner’s fix is the most correct one; @osdiab’s pnpm patch approach works great here. Thanks both!

It’s a shame that it’s taken years for Microsoft to resolve this, since Microsoft has long helped to guide the OAuth and OIDC specification work. Big companies, I guess. 🙃

As a note to the next-auth authors, while I applaud your stance urging conformance to the spec, I’d encourage you to bake in the workaround (even if it’s via a snarky flag like enableWorkaroundForMicrosoftNonCompliantImplementation). I’m the original creator of OAuth, and the goal was always to (1) simplify the user experience for users and (2) create secure, reusable, open-source implementations.

Ultimately, those of us who find our way to this issue are just trying to get people signed in, and most aren’t concerned with the state of Microsoft’s standards compliance. Resorting to one-off hacks in many codebases, versus a “supported” workaround will leave us all less secure.

Keep pressing on Microsoft, though! They should know better, but encouraging your users to leave reports on their issue tracker will have an effect. Also, thanks for your work on next-auth! 💜

Welllll for now I’ll do this stupid dirty hack.

  • I made a patch for oauth4webapi that just ignores the issuer check if it happens to be a specific Microsoft common tenant issuer; here is what the patch looks like:

https://github.com/panva/oauth4webapi/compare/main...osdiab:oauth4webapi:lax-for-microsoft?expand=1&w=1

  • Then I used a pnpm patch to apply this change
  • make sure oauth4webapi is explicitly declared as a dependency in your package.json

Patch file (last updated 2023-12-14 to apply @cbdabner 's suggestion):

oauth4webapi@2.3.0.patch

How to use:

  • put this file in a patches folder in the root of your repo
  • add the following to pnpm config (replace 2.3.0 in the key with whatever version is actually installed in your repo, this is the version as of now. it’s ok if the patch file is not called 2.3.0 so long as it applies properly):
{
  // ...
  "pnpm": {
    "patchedDependencies": {
      "oauth4webapi@2.3.0": "patches/oauth4webapi@2.3.0.patch"
    },
  }
}

CAUTION: if you bump the oauth4webapi version, make sure you also bump it in this version here, or else the patch will no longer apply

and for completeness for anyone who is unsure how to use this, this is how I set up the Azure auth when instantiating AuthJS:

// we use the common tenant to accept any Microsoft account
const microsoftTenantId = "common";
const microsoftConfig = AzureADProvider({
  // copied from the Essentials section in the Azure Console, Microsoft Entra ID,
  // make an App Registration, get the client ID after done with that
  clientId: process.env["AZURE_AD_CLIENT_ID"], 
  // In the App Registration, click on the Client Secrets link, make a secret
  clientSecret: process.env["AZURE_AD_CLIENT_SECRET"],
  tenantId: microsoftTenantId,
  // need to override these URLs to skip the Discovery phase, to sidestep OIDC
  // validation issues
  token: {
    url: `https://login.microsoftonline.com/${microsoftTenantId}/oauth2/v2.0/token`,
  },
  userinfo: { url: "https://graph.microsoft.com/oidc/userinfo" },
  authorization: {
    url: `https://login.microsoftonline.com/${microsoftTenantId}/oauth2/v2.0/authorize`,
    params: { scope: "openid profile email User.Read" },
  },
  issuer: `https://login.microsoftonline.com/${microsoftTenantId}/v2.0`,
});

~I would appreciate if anyone who actually understands OIDC security could suggest other checks that would be good to have instead of just dropping the issuer check altogether, or to advise if this is actually safe to skip. I am unaware of the negative security impact of just skipping the issuer check entirely.~ Thanks for the support @cbdabner and @blaine !

Aside: This doesn’t check for a sts.windows.net issuer since you shouldn’t get that value so long as the acceptedTokenVersion is 2 in your Microsoft auth config, which it should be if you’re setting it up with defaults

So is it the case that the common tenant ID (and therefore using next-auth/AuthJS with Microsoft Login for general, non-enterprise users) is fundamentally unusable?

@balazsorban44 it would be very helpful if in the documentation for Azure AD, this was plastered in big letters that AuthJS is fundamentally incompatible with Microsoft Login, and if you care about Microsoft authentication at all you should use a different library. It is very misleading to claim that next-auth is a general purpose auth library and yet refuse to support one of the biggest auth providers, regardless of their standards-noncompliant nature.

It’s looking like either I need to fork next-auth and roll my own security; or stop using next-auth altogether and migrate my company away from next-auth, either of which will be a big pain 😮‍💨

I updated my post to include the suggested fix!

@RonB The issue with your solution is that the issuer is different based on the domain or if its public. For examples the issuer for peter@pan.com and peter@live.com will have different values. For some reason the system is not returning common as its issuer, instead its returning the owner of the id as the issuer.

+1 I’m having the same exact issue, and have tried @georgy’s explicit definition for authorization and token, get the same exact response.