Skip to content

Commit

Permalink
enabling multi-tenant functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
skilesare committed Nov 24, 2020
1 parent 5126f4e commit 86ea3de
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 21 deletions.
32 changes: 23 additions & 9 deletions src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import parseUrl from '../lib/parse-url'
// relative URLs are valid in that context and so defaults to empty.
// 2. When invoked server side the value is picked up from an environment
// variable and defaults to 'http://localhost:3000'.
const multiTenant = process.env.MULTITENANT === "true"
const __NEXTAUTH = {
baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
basePath: parseUrl(process.env.NEXTAUTH_URL).basePath,
multiTenant: multiTenant,
keepAlive: 0, // 0 == disabled (don't send); 60 == send every 60 seconds
clientMaxAge: 0, // 0 == disabled (only use cache); 60 == sync if last checked > 60 seconds ago
// Properties starting with _ are used for tracking internal app state
Expand Down Expand Up @@ -80,11 +82,13 @@ const setOptions = ({
baseUrl,
basePath,
clientMaxAge,
keepAlive
keepAlive,
multiTenant
} = {}) => {
if (baseUrl) { __NEXTAUTH.baseUrl = baseUrl }
if (basePath) { __NEXTAUTH.basePath = basePath }
if (clientMaxAge) { __NEXTAUTH.clientMaxAge = clientMaxAge }
if (multiTenant) { __NEXTAUTH.multiTenant = multiTenant}
if (keepAlive) {
__NEXTAUTH.keepAlive = keepAlive

Expand All @@ -110,7 +114,7 @@ const getSession = async ({ req, ctx, triggerEvent = true } = {}) => {
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
if (!req && ctx && ctx.req) { req = ctx.req }

const baseUrl = _apiBaseUrl()
const baseUrl = _apiBaseUrl(req)
const fetchOptions = req ? { headers: { cookie: req.headers.cookie } } : {}
const session = await _fetchData(`${baseUrl}/session`, fetchOptions)
if (triggerEvent) {
Expand All @@ -126,15 +130,15 @@ const getCsrfToken = async ({ req, ctx } = {}) => {
// work seemlessly in getInitialProps() on server side pages *and* in _app.js.
if (!req && ctx && ctx.req) { req = ctx.req }

const baseUrl = _apiBaseUrl()
const baseUrl = _apiBaseUrl(req)
const fetchOptions = req ? { headers: { cookie: req.headers.cookie } } : {}
const data = await _fetchData(`${baseUrl}/csrf`, fetchOptions)
return data && data.csrfToken ? data.csrfToken : null
}

// Universal method (client + server); does not require request headers
const getProviders = async () => {
const baseUrl = _apiBaseUrl()
// Universal method (client + server); does not require request headers but seems to only be called by client
const getProviders = async (req) => {
const baseUrl = _apiBaseUrl(req)
return _fetchData(`${baseUrl}/providers`)
}

Expand Down Expand Up @@ -294,13 +298,23 @@ const _fetchData = async (url, options = {}) => {
}
}

const _apiBaseUrl = () => {
const _apiBaseUrl = (req) => {
if (typeof window === 'undefined') {
// NEXTAUTH_URL should always be set explicitly to support server side calls - log warning if not set
if (!process.env.NEXTAUTH_URL) { logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set') }
if (!__NEXTAUTH.multiTenant && !process.env.NEXTAUTH_URL) { logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set') }

// Return absolute path when called server side
return `${__NEXTAUTH.baseUrl}${__NEXTAUTH.basePath}`
if(req && __NEXTAUTH.multiTenant){
let protocol = 'http'
if( (req.headers.referer && req.headers.referer.split("://")[0] == 'https') || (req.headers['X-Forwarded-Proto'] && req.headers['X-Forwarded-Proto'] === 'https')){
protocol = 'https'
}
return protocol + "://" +`${req.headers.host}${__NEXTAUTH.basePath}`
} else if(__NEXTAUTH.multiTenant){
logger.warn('found an instance of multitenant without a req')
} else {
return `${__NEXTAUTH.baseUrl}${__NEXTAUTH.basePath}`
}
} else {
// Return relative path when called client side
return __NEXTAUTH.basePath
Expand Down
11 changes: 10 additions & 1 deletion src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,16 @@ export default async (req, res, userSuppliedOptions) => {
} = body

// @todo refactor all existing references to site, baseUrl and basePath
const parsedUrl = parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL)
let multiTenantURL = null
if(process.env.MULTITENANT == "true"){
let protocol = 'http'
if( (req.headers.referer && req.headers.referer.split("://")[0] == 'https') || (req.headers['X-Forwarded-Proto'] && req.headers['X-Forwarded-Proto'] === 'https')){
protocol = 'https'
}

multiTenantURL = protocol + "://" + req.headers.host
}
const parsedUrl = parseUrl(multiTenantURL || process.env.NEXTAUTH_URL || process.env.VERCEL_URL)
const baseUrl = parsedUrl.baseUrl
const basePath = parsedUrl.basePath

Expand Down
2 changes: 1 addition & 1 deletion src/server/lib/callbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const redirect = async (url, baseUrl) => {
* @param {object} token JSON Web Token (if enabled)
* @return {object} Session that will be returned to the client
*/
const session = async (session, token) => {
const session = async (session, token, req) => {
return Promise.resolve(session)
}

Expand Down
4 changes: 2 additions & 2 deletions src/server/routes/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default async (req, res, options, done) => {

// Pass Session and JSON Web Token through to the session callback
const jwtPayload = await callbacks.jwt(decodedJwt)
const sessionPayload = await callbacks.session(defaultSessionPayload, jwtPayload)
const sessionPayload = await callbacks.session(defaultSessionPayload, jwtPayload, req)

// Return session payload as response
response = sessionPayload
Expand Down Expand Up @@ -79,7 +79,7 @@ export default async (req, res, options, done) => {
}

// Pass Session through to the session callback
const sessionPayload = await callbacks.session(defaultSessionPayload, user)
const sessionPayload = await callbacks.session(defaultSessionPayload, user, req)

// Return session payload as response
response = sessionPayload
Expand Down
30 changes: 22 additions & 8 deletions src/server/routes/signin.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,34 @@ export default async (req, res, options, done) => {
return done()
}

const _baseUrl = function(){
if(process.env.MULTITENANT == "true"){
let protocol = 'http'
if( (req.headers.referer && req.headers.referer.split("://")[0] == 'https') || (req.headers['X-Forwarded-Proto'] && req.headers['X-Forwarded-Proto'] === 'https')){
protocol = 'https'
}
return protocol + "://" + req.headers.host + `${basePath}`
} else {
return `${baseUrl}${basePath}`
}
}

// Adding to handle multi tenant solutions where the base url changes


if (type === 'oauth' && req.method === 'POST') {
oAuthSignin(provider, csrfToken, (error, oAuthSigninUrl) => {
if (error) {
logger.error('SIGNIN_OAUTH_ERROR', error)
return redirect(`${baseUrl}${basePath}/error?error=OAuthSignin`)
return redirect(_baseUrl() + `/error?error=OAuthSignin`)
}

return redirect(oAuthSigninUrl)
})
} else if (type === 'email' && req.method === 'POST') {
if (!adapter) {
logger.error('EMAIL_REQUIRES_ADAPTER_ERROR')
return redirect(`${baseUrl}${basePath}/error?error=Configuration`)
return redirect(_baseUrl() + `/error?error=Configuration`)
}
const { getUserByEmail } = await adapter.getAdapter(options)

Expand All @@ -53,11 +68,11 @@ export default async (req, res, options, done) => {
try {
const signinCallbackResponse = await callbacks.signIn(profile, account, { email, verificationRequest: true })
if (signinCallbackResponse === false) {
return redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
return redirect(_baseUrl() + `/error?error=AccessDenied`)
}
} catch (error) {
if (error instanceof Error) {
return redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
return redirect(_baseUrl() + `/error?error=${encodeURIComponent(error)}`)
} else {
return redirect(error)
}
Expand All @@ -67,13 +82,12 @@ export default async (req, res, options, done) => {
await emailSignin(email, provider, options)
} catch (error) {
logger.error('SIGNIN_EMAIL_ERROR', error)
return redirect(`${baseUrl}${basePath}/error?error=EmailSignin`)
return redirect(_baseUrl() + `/error?error=EmailSignin`)
}

return redirect(`${baseUrl}${basePath}/verify-request?provider=${encodeURIComponent(
return redirect(_baseUrl() + `/verify-request?provider=${encodeURIComponent(
provider.id
)}&type=${encodeURIComponent(provider.type)}`)
} else {
return redirect(`${baseUrl}${basePath}/signin`)
return redirect(_baseUrl() + `_baseUrl() + /signin`)
}
}

1 comment on commit 86ea3de

@payzly-dev
Copy link

Choose a reason for hiding this comment

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

Hello @skilesare,

It is very nifty that you were able to make nextauth work for two different domains attached to the same DB. Any chance you could help answer two questions that I have so that I can attempt to replicate your success?

1. Do you think that your solution will work for one domain, but with multiple subdomains?

Example:

https://clientone.mydomainname.com

https://clienttwo.mydomainname.com

2. Possible to share the names and values of your environment variables for production that allowed your solution to work so that I can try to reproduce using the same names but with my values?

Hope you are well!

Please sign in to comment.