Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Need to set NEXTAUTH_URL dynamically as an Option for multi-domain/multi-tenant use #600

Closed
2 of 5 tasks
SharadKumar opened this issue Aug 24, 2020 · 145 comments
Closed
2 of 5 tasks
Labels
question Ask how to do something or how something works

Comments

@SharadKumar
Copy link

Your question
How to dynamically work with Passwordless/Email auth, without setting NEXTAUTH_URL.

What are you trying to do
I am working on a use-case where NEXTAUTH_URL is not fixed at deploy-time or build-time, but run-time, for a multi-domain (single codebase) scenario. This is to have Email passwordless only.

I have had good success with next-auth other providers for usual scenarios, and absolutely love the simplicity.

Feedback
I tried to browse around the code to get a sense of dependency of the deploy-time NEXTAUTH_URL, and it seems that it is use only to define the Url for sendVerificationRequest. If there was a way to pass it as an option, it would do it.

Please advise, whats the best approach.

  • 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
@SharadKumar SharadKumar added the question Ask how to do something or how something works label Aug 24, 2020
@SharadKumar
Copy link
Author

Server has a todo flagged:

// @todo refactor all existing references to site, baseUrl and basePath
const parsedUrl = parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL)
const baseUrl = parsedUrl.baseUrl
const basePath = parsedUrl.basePath

JWT. GetToken can be custom though:

next-auth/src/lib/jwt.js

Lines 94 to 103 in 8115a7c

const getToken = async (args) => {
const {
req,
// Use secure prefix for cookie name, unless URL is NEXTAUTH_URL is http://
// or not set (e.g. development or test instance) case use unprefixed name
secureCookie = !(!process.env.NEXTAUTH_URL || process.env.NEXTAUTH_URL.startsWith('http://')),
cookieName = (secureCookie) ? '__Secure-next-auth.session-token' : 'next-auth.session-token',
raw = false
} = args
if (!req) throw new Error('Must pass `req` to JWT getToken()')

And, for the client:

const __NEXTAUTH = {
baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
basePath: parseUrl(process.env.NEXTAUTH_URL).basePath,

These are the references I found. @iaincollins I can try to fork and attempt a hack on my side, since I really need to solve this. If you can advise how to go about it, it would be awesome. It seems you intend to refine/refactor this at some point, as stated above. Above three are the only references I found.

Any help is really appreciated.

@SharadKumar
Copy link
Author

SharadKumar commented Aug 26, 2020

Okay, so I managed to fork and make server-side work! Looking into the client side now...

I added to server:

    const { origin } = absoluteUrl(req)

    // @todo refactor all existing references to site, baseUrl and basePath
    const parsedUrl = parseUrl(process.env.NEXTAUTH_URL || origin || process.env.VERCEL_URL)

and lib/parse-url.js: (I couldn't find a way to get protocol with NextJS req object:

export const absoluteUrl = (req) => {
  var protocol = "https:"
  var host = req
    ? req.headers["x-forwarded-host"] || req.headers["host"]
    : window.location.host

  if (host.indexOf("localhost") > -1 || host.indexOf(".local") > -1) {
    protocol = "http:"
  }
  return {
    protocol: protocol,
    host: host,
    origin: protocol + "//" + host,
  }
}

@iaincollins
Copy link
Member

Hi Sharad,

If you are seeing this error there is likely problem with your build process or how you are linking to the libraries. You'd need to post and link to the repo before we can help with that.

Regarding the wider question of supporting domains dynamically, this is something we don't support currently. I don't have an update on this I'm ready to share right now, but I hope there will be an update on this at some point in the coming weeks.

@SharadKumar
Copy link
Author

Thanks Iain, yes I am trying to get my head around this.
I've been able to work with multiple-domains dynamically, and to do that I forked... modified server to work with origin (as above) based on current request. At the moment, my main use case is Email provider.

I also started my own Adapter, based on your prisma adapter... to support multi-tenancy in database. Pretty much everything you have as-is, plus associating users with a Business (tenant) record.

Excellent work with next-auth. So helpful.

@dhalbrook
Copy link

We really need this as well, as we have a multi-tenant Next app in the works.

It seems like you could take in a callback function in the userSuppliedOptions of server/index.js that would pass the req object in, like userSuppliedOptions.baseUrlSupplier(req), to allow everyone to specify the base URL as needed from the request (in our cases tenancy has more than one potential marker). Not sure of the best way to get the derived value to jwt.js:99 though.

@dolie
Copy link

dolie commented Oct 19, 2020

Our application is multi-tenant as well. We also need(ed) this feature.
We decided to deploy one environment for each tenants to resolve this issue.

@ChuckJonas
Copy link

ChuckJonas commented Nov 21, 2020

I also need this to be able to properly setup salesforce as a custom oAuth2 provider:

https://${instanceUrl}.salesforce.com/services/oauth2/token

There are cases where the user will need to specify their "subdomain" in order to be able to login.

@skilesare
Copy link

I've tried to address this here: skilesare@86ea3de

It may need some changes....and I only really focused on the session call back because I needed to get some data based on the domain in the session, but the pattern should be easy to follow for the other callbacks. Basically the request (req) just needs to be passed to these callbacks so that you can key in on the domain/sub domain. The other potential gotcha is the http vs https so if anyone has a suggestion for that, let me know.

If any one wants to walk me through how to get this set up so that it can eventually be pulled in, let me know as well...haven't contributed to projects this big before and don't want to step on any toes.

@trollr
Copy link

trollr commented Dec 1, 2020

Our application is multi-tenant as well. We also need(ed) this feature. I would like to be able to simply set the redirect_url in the provider options

@ramiel
Copy link
Contributor

ramiel commented Dec 3, 2020

One good reason to think to support multi-tenant is that nex-auth is likely to be deployed on vercel. On vercel each deployment has several domains (a lot more if the user defined aliases). Since this is a project for next.js, supporting Vercel looks natural to me.

@specialfae

This comment has been minimized.

@slumbering

This comment has been minimized.

@jukbot

This comment has been minimized.

@highflying

This comment has been minimized.

@balazsorban44
Copy link
Member

To anyone reading this, please stop +1 issues, it is really not helpful, and creates noise. 😕

If you have nothing constructive to add, do click "👍" on the original comment instead.

@stale
Copy link

stale bot commented Mar 11, 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 Mar 11, 2021
@balazsorban44
Copy link
Member

balazsorban44 commented Mar 11, 2021

Could someone please confirm that this new option resolves this issue?

https://next-auth.js.org/configuration/options#nextauth_url_internal

@stale stale bot removed the stale Did not receive any activity for 60 days label Mar 11, 2021
@Robert-OP
Copy link

Hey @balazsorban44

I cannot think or figure out how https://next-auth.js.org/configuration/options#nextauth_url_internal would resolve handling multiple domains, for example, having 3 domains [.com, .io, .dk] and switching between these signing into each of them.

Do you think this may be possible with NEXTAUTH_URL_INTERNAL? 🤔

BTW I started some work regarding multi-tenancy based on what has been discussed here and @skilesare 's work in #969 (comment).

@balazsorban44
Copy link
Member

I see, thanks for the update! To be honest, I haven't really looked into this use-case yet, so I wasn't entirely sure what was needed. So I am not going to close this for now.

@Robert-OP
Copy link

@balazsorban44 - is there a place to talk (besides GitHub, like a Slack/Discord) for NextAuth community? Also, could you point me out to docs on how to best test, build, and package my solution to do something in my current environment? I was thinking of building the fork and just upload it to GitHub packages.

@balazsorban44
Copy link
Member

balazsorban44 commented Mar 13, 2021

Either GitHub packages or npm would do. We use semantic release for automatic releases based on commit messages. The best place for community discussions are here in issues or alternatively discussions, as these are the public places where everyone can see any part of the discussion.

@Robert-OP
Copy link

Robert-OP commented Mar 13, 2021

I already posted a multi-tenant version (incomplete, work in progress) on npm that seems to work for my use case to some extent, still working on it and will improve. With this you need to set MULTI_TENANT="true" in environment variables (.env) such that it's picked up by the library and goes into multi-tenant mode. NOTE that the domains whitelisting still need to be included.

@andresgutgon
Copy link

Hi @Robert-OP thanks for your work. I don't see a PR for these changes in this repo, did you opened it?

@andresgutgon
Copy link

andresgutgon commented May 2, 2021

For the whitelisting couldn't be inferred from i18n config in next? https://nextjs.org/docs/advanced-features/i18n-routing ?

I guess this list is not useful for the Vercel preview use case but as @thulstrup commented could be a regexp check (option 2)

So whitelisting could be in these 2 ways:

1. i18n Next config (developer change these URL for their dev env or their staging or production)
2. Check a regexp like this: https://{process.env.VERCEL_APP}\-*.\-{process.env.VERCEL_TEAM}\.vercel\.app

I never worked with Vercel but I guess setting app and team as fixed is safe.

@Robert-OP
Copy link

Hey @andresgutgon hope you are well!

I didn't open a PR because the work is not finished yet, I didn't add the whitelisting of domains and I see that #969 (comment) it's not that of a good idea at least for Vercel.

The solution above works, but I think still needs a review from the next-auth team since I might overseen some important matters. 😬

@sylvainbaronnet
Copy link

sylvainbaronnet commented Aug 2, 2023

Sorry but I'm really confused how a major issue like this is still unresolved in 2 years, it basically makes this lib unusable.

Is there anything I'm missing ? Am I just well understanding that I shouldn't be using this lib unless I choose to host it on Vercel ?

Is this real ?

@sylvainbaronnet
Copy link

sylvainbaronnet commented Aug 2, 2023

so much about open source... more like vendor-locked source

I know what hosting I MUST not use

EDIT: it's your right to do so, just be clear & open about it

@Rieranthony
Copy link

After spending a lot of time to find the right solution using Next Auth, I decided to use something else. NextAuth is really cool, but as soon as you need something more custom or advanced, it becomes clear that you can't really do it easily.

What I've done, if anyone are looking for a solution, I ended using the amazing iron-session library, which essentially gives you all the low-level tools to achieve something really powerful.

I'm now having a multi tenant app, working across multiple subdomain and domain names using the same code, it's not impossible but require more thoughts!

Good luck to everyone and thanks to the next auth team, the library is incredible in many many ways ;)

@rahul-reveation
Copy link

@Rieranthony Are using Next.js v13.x with iron-session v8-alpha package for your multi-tenant solution?

@Rieranthony
Copy link

I'm using v8 with a custom implementation :)

@dkdevpro
Copy link

dkdevpro commented Sep 6, 2023

As a newbie to NextJS, i was surprised that this issue which is important yet no fix in place. I spent around 2 days to understand and figure out the way to add fix. I did the following code fix and it started allowing for multi-domain/multi-tenant.

export function detectOrigin(forwardedHost: any, protocol: any) {
// If we detect a Vercel environment, we can trust the host
if (process.env.VERCEL ?? process.env.AUTH_TRUST_HOST)
return `${protocol === "http" ? "http" : "https"}://${forwardedHost}`
// If `NEXTAUTH_URL` is `undefined` we fall back to "http://localhost:3000"
return process.env.NEXTAUTH_URL
}

export function detectOrigin(forwardedHost: any, protocol: any) {
  // If we detect a Vercel environment, we can trust the host
  if (process.env.VERCEL ?? process.env.AUTH_TRUST_HOST)
    return `${protocol === "http" ? "http" : "https"}://${forwardedHost}`

  // If `NEXTAUTH_URL` is `undefined` we fall back to "http://localhost:3000"
   return `${protocol === "http" ? "http" : "https"}://${forwardedHost}`
}

@theishinz-maersk
Copy link

Setting the value on process.env does not work if you want change NEXTAUTH_URL per request as it isn't thread safe.
Have any been involved with an official maintainer and know if they would accept if NEXTAUTH_URL could be set as a parameter instead. Or allow a list.

@theishinz-maersk As per Node.js documentation, A single instance of Node.js runs in a single thread. and JavaScript is single-threaded so how does thread safety work in this case?

Hi @rajat1saxena. The thread is reused for multiple requests and it swaps between the calls whenever you use promises.
Like, your Nodejs application is still available for other requests if it in the meantime is waiting on a promise to be resolved.

@rayhantr
Copy link

rayhantr commented Oct 5, 2023

I don't know if this helps someone, but the below code works for me. I am using next: 13.4.20-canary.15 and next-auth: 4.22.1. I deployed my project into vercel. Now if I go to tenant1.mydomain.com, tenant2.mydomain.com I am able to login for them separately. Note that I have set custom domain in my vercel domain. Also I am following the codes from Vercel Platform Kit which is using Next13.

lib/auth.ts

export const authOptions = (req: NextApiRequest): NextAuthOptions => {
  // Need to ts-ignore this because the type `NextApiRequest` doesn't have the `get` method
  // And we need the `get` method to get the hostname.
  // @ts-ignore
  const hostname = req.headers.get("host");

  return {
    providers: [...],
    pages: {...},
    session: { strategy: "jwt" },
    cookies: {
      sessionToken: {
        name: `${VERCEL_DEPLOYMENT ? "__Secure-" : ""}next-auth.session-token`,
        options: {
          httpOnly: true,
          sameSite: "lax",
          path: "/",
          // This is what works for me
          domain: hostname.includes("localhost:3000") ? undefined : hostname,
          secure: VERCEL_DEPLOYMENT,
        },
      },
    },
    callbacks: {...},
  };
};

app/api/auth/[...nextauth]/route.ts

import { authOptions } from "@/lib/auth";
import { type NextApiResponse, type NextApiRequest } from "next";
import NextAuth from "next-auth";

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  return await NextAuth(req, res, authOptions(req));
};

export { handler as GET, handler as POST };

Note: Here NextAuth takes only NextApiRequest & NextApiResponse types as arguments for the req & res. But in next 13 route handler the req and res types should be Web Request and Response APIs with extended NextRequest and NextResponse types provided by Nextjs.

@mrviniciux
Copy link

mrviniciux commented Oct 11, 2023

"next": "^13.4.12",
"next-auth": "4.20.1",

Following the Custom Initialization docs: https://authjs.dev/guides/basics/initialization and some other suggested stuff above I found a way to change NEXTAUTH_URL on runtime.

/.env

NEXTAUTH_SECRET=34265456345756754745
NEXTAUTH_URL="localhost:3000"

/src/pages/api/auth/[...nextauth].ts

export default async function auth(req: NextApiRequest, res: NextApiResponse) {
  const { host } = req.headers;

  //that worked for me
  process.env.NEXTAUTH_URL = /localhost/.test(host || '')
    ? `http://${host}`
    : host;

  const config = await NextAuth(req, res, {
    providers: [],
    events: {},
    callbacks: {}
  }

Not sure if it's the best approach, but it works on a scenario where the Github actions trigger a k8s deployment after opening a PR. Usually those deployments have a PR id attached to his address, something like: https://473-my-app.domain.com where 473 is the PR id.

@basememara
Copy link

basememara commented Oct 20, 2023

I'm using 4.22.1 and trying to achieve multi-tenancy as well; not hosted on Vercel. I added the env var AUTH_TRUST_HOST=true and it seemed to work, but not sure what implication this has since it isn't documented or any mention of multi-tenancy. Still define NEXTAUTH_URL with your primary URL which seems to be used for non-domain things like base path or protocol.

I had to dynamically setup my cookie though in [...nextauth].ts:

const authOptions = (request: NextApiRequest) => {
  const useSecureCookies = process.env.NEXTAUTH_URL?.startsWith('https://') ?? !!process.env.AUTH_TRUST_HOST;
  const cookiePrefix = useSecureCookies ? '__Secure-' : '';
  const cookieDomain = useSecureCookies ? new URL(`http://${request.headers.host}`).hostname : null;

  return {
    ...
    cookies: {
      sessionToken: {
        name: `${cookiePrefix}next-auth.session-token`,
        options: {
          httpOnly: true,
          sameSite: 'lax',
          path: '/',
          secure: useSecureCookies,
          ...(cookieDomain && { domain: cookieDomain })
        }
      }
    },
    ...
  } satisfies AuthOptions;
};

export default async function auth(req: NextApiRequest, res: NextApiResponse) {
  return NextAuth(req, res, authOptions(req));
}

Anyone doing something similar in production and share any experience or insight? Also, why is multi-tenancy seemingly ignored by the NextAuth team even though it is functionally almost there? A mention in the doc acknowledging that multi-tenancy needs exist would be deeply appreciated. Going through 100+ comments over 3 years about this topic with no clear solution.

Closed PRs:

NEXTAUTH_URL in codebase:

@jwgmeligmeyling
Copy link

Its not ignored, its support was deliberately removed to not have to consider ways to make it secure. Time spent researching how to get multi tenancy to work is probably better spent looking for alternatives.

@basememara
Copy link

Forgive my assumption, thx for the context. I totally get multi-tenancy exponentiates complexity especially with a security product. I just wanted to explore the extensibility of NextAuth before deciding to abandon it. Honestly though, we prefer to continue to invest and contribute to it since it’s such a great foundation.

Would something simple as converting NEXTAUTH_URL to an array or regex resolve the security concerns and still give us what we need?

I’m hoping for a little bit more guidance or insight on how and where the community can jump in. Possibly this can be a guide that lives outside the core offerings like the approach of role-based access control.

@syedAwadh
Copy link

I have faced the same issue but I have found a solution which was working fine for me by just updating the existing .env as follows :

  1. remove the NEXTAUTH_URL from .env
  2. add AUTH_TRUST_HOST=true to .env

@wildfrontend
Copy link

I have faced the same issue but I have found a solution which was working fine for me by just updating the existing .env as follows :

  1. remove the NEXTAUTH_URL from .env
  2. add AUTH_TRUST_HOST=true to .env

where have AUTH_TRUST_HOST env setting? i can not find doc at next-auth

@mauron85
Copy link

mauron85 commented Nov 15, 2023

The problem with AUTH_TRUST_HOST is that it doesn't respect basePath option of nextjs. And it's because parse-url uses defaultUrl in when AUTH_TRUST_HOST is true. There should either option eg. AUTH_TRUST_PATHNAME or next-auth should parse NEXTAUTH_URL for the basePath.

const defaultUrl = new URL("http://localhost:3000/api/auth")

@wildfrontend
Copy link

wildfrontend commented Nov 16, 2023

it is big problem , because NEXTAUTH_URL is for client side url, and client side url could be multi domain, it would be cause wrong domain redirect , so baseUrl should not be static variable , or maybe set a option in authOptions to change baseUrl

export function apiBaseUrl(__NEXTAUTH: AuthClientConfig) {

https://github.com/nextauthjs/next-auth/blob/cf9712e784a9075622ce18bdb586f86223073226/packages/next-auth/src/react.tsx#L56C26-L56C26

@balazsorban44

@mauron85
Copy link

mauron85 commented Nov 20, 2023

@wildfrontend I see there is quite big refactor in v5 regarding this. I was referring to v4. The parse-url is gone and instead I see:

export function reqWithEnvUrl(req: NextRequest): NextRequest {

Also I was talking about backend api/auth/providers not respecting basePath option and generating callback and redirect url without basePath.

If your issue is on client side. You can get basePath from runtime config in _app.tsx:

        import getConfig from 'next/config';

        export default function MyApp({ Component, pageProps: { session, ...pageProps } } {
          const { publicRuntimeConfig: c } = getConfig();
  
          return (
		  <SessionProvider
			  session={session}
			  basePath={`${c.basePath}/api/auth`}
		  >{...}
		  </SessionProvider>
           );
       }

in next.config.js you can set basePath from env param and also expose same value for runtime config (used in _app.tsx - as above)

const nextConfig = {
	basePath: process.env.NEXT_RT_BASEPATH,
	publicRuntimeConfig: {
		basePath: process.env.NEXT_RT_BASEPATH ?? '',
	}
}

@mauron85
Copy link

mauron85 commented Nov 20, 2023

Very ugly workaround for v4 basePath issue when AUTH_TRUST_HOST is true:

Since req.origin is set by toInternalRequest function (detectOrigin) reading x-forwarded-host header and then req.origin is used in parseUrl, but since x-forwarded-host doesn't contain any paths, we need to add it, so parseUrl respects basePath.

pages\api\auth[...nextauth].ts

export default async function auth(req: NextApiRequest, res: NextApiResponse) {
	// Info: Workaround for providers callback signinUrl and callbackUrl not including basePath
	if (req.headers['x-forwarded-host']) {
		req.headers['x-forwarded-host'] = `${req.headers['x-forwarded-host']}${process.env.NEXT_RT_BASEPATH}/api/auth`;
	}

	return await NextAuth(req, res, { ...options });
}

@ZainXalid
Copy link

Hey guys, (@mauron85, @wildfrontend)
What do you think about this approach for NextJS 13? Will it work for both sub and custom domains?

https://www.skcript.com/blog/how-to-build-multi-tenant-auth-nextjs

@brunoalano
Copy link

Any workaround for next-auth@v5?

@aman-godara
Copy link

aman-godara commented Dec 16, 2023

  1. setting env variable AUTH_TRUST_HOST=true didn't work
  2. dynamically setting process.env.NEXTAUTH_URL (with advanced initialization) worked but isn't thread safe (you can get its code from here: Dynamic NEXTAUTH_URL #969)
  3. custom solutions built by @Robert-OP & @skilesare are too old
  4. use next-auth on a subdomain as a separated authentication service #1299 & How to manage multiple domains with NextAuth.js? #4022 are hard to implement

@osiegmar
Copy link

@balazsorban44 Given the fact that this issue is by far the most upvoted one and was opened three years ago, I would like to kindly ask if it is possible to get a bit more attention?

@basememara
Copy link

Vercel published a multi-tenant Platforms Starter Kit using Next.js and NextAuth v4. Seems like it's hardcoded to Vercel hosting though: https://vercel.com/templates/next.js/platforms-starter-kit (repo)

@LoganArnett
Copy link

Same question on the v5 front, trying to accomplish this and the NextAuth default export no longer accepts the req, res

@ZainXalid
Copy link

Short Answer: It's not supported.

Some devs may find a work around but if you don't know what you're doing then please look for an alternative option. I wasted a lot of time looking for it and, in the end, had to look for an alternative option.

Clerk Auth supports it otherwise go for a custom auth solution.

@do4Mother
Copy link

do4Mother commented Jan 23, 2024

i solve this issue by adding process.env.AUTH_TRUST_HOST = true and process.env.VERCEL = true

edit SessionProvider and add baseUrl and basePath (baseURL + '/api/auth')

to get baseUrl you can use this code

  const getHeader = headers();
  const protocol = getHeader.get("x-forwarded-proto") ?? "";
  const host = getHeader.get("X-Forwarded-Host") ?? "";

@nextauthjs nextauthjs locked and limited conversation to collaborators Jan 25, 2024
@balazsorban44 balazsorban44 converted this issue into discussion #9785 Jan 25, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Ask how to do something or how something works
Projects
None yet
Development

No branches or pull requests