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

Deploy under multiple sub domains #16

Open
7 tasks
ShayElkana opened this issue Mar 4, 2020 · 12 comments
Open
7 tasks

Deploy under multiple sub domains #16

ShayElkana opened this issue Mar 4, 2020 · 12 comments

Comments

@ShayElkana
Copy link

I'm submitting this issue for the package(s):

I'm submitting a:

  • Bug report
  • [ V] Feature request
  • Other (Describe below)

Current behavior

Today we have to declare at app init what is the app appBaseUrl as you can see below:
const oidc = new ExpressOIDC({
issuer: 'https://{yourOktaDomain}/oauth2/default',
client_id: '{clientId}',
client_secret: '{clientSecret}',
appBaseUrl: '{appBaseUrl}',
scope: 'openid profile'
});
Which limits us to deploy each instance of our application under a single domain.

Expected behavior

We wish to deploy our system into a set of servers under a single load balancer, but we are going to have a few subdomains which all of them are pointing to the same load balancer and from there to the same web application, please see the sketch below:

Okta node middleware ExpressOIDC appBaseUrl parameter - shay elkana@nielsen com - Nielsen Mail 2020-03-04 10-15-22(1)

we wish that the appBaseUrl will be optional since okta middleware can take the appBaseUrl from the host header on each HTTP request, and then it will be possible to use the same app under more than one appBaseUrl

@aarongranick-okta
Copy link
Contributor

@ShayElkana The appBaseUrl is used for constructing the loginRedirectUrl and the logoutRedirectUrl. These URLs are not used by our module directly but are passed to the
openid-client module which handles the OIDC redirect flow. We are also using the
passport module to provide session. Although we do not support this scenario directly, I think with some clever coding you could adapt these modules to use a url from the header to select a redirect uri from a known list. Keep in mind that all redirect uris will need to be whitelisted with Okta, so there is some limit to how dynamic it can be.

Also worth thinking about for this scenario is that the OIDC flow is not stateless, a state & nonce value are saved before redirect and validated upon return, so the servers will need to share a session memory or have some other way of pinning a user to a specific server.

We appreciate the request for enhancement. If you have some specific ideas about how to implement this using our middleware, you are welcome to fork the project and we will be happy to consider a pull request.

@LiranBri
Copy link

I found a workaround that allow you to initialize the middleware multiple times for different domain.
basicaly, the problem is that the library has a singelton nature. I force it to load different instance by doing:

  delete require.cache[require.resolve('passport')]
  delete require.cache[require.resolve('@okta/oidc-middleware/src/oidcUtil.js')]
  delete require.cache[require.resolve('@okta/oidc-middleware/src/connectUtil.js')]
  delete require.cache[require.resolve('@okta/oidc-middleware/src/ExpressOIDC.js')]
  delete require.cache[require.resolve('@okta/oidc-middleware')]
  const { ExpressOIDC } = require('@okta/oidc-middleware')

I do that before each initialization.

for example:

  const baseMiddlwwareOptions = {
    issuer: `${OKTA_BASE_URL}/oauth2/default`,
    client_id: OKTA_CLIENT_ID,
    client_secret: OKTA_CLIENT_SECRET,
    scope: 'openid profile'
  }

  const lib1 = require('@okta/oidc-middleware')
  const oidcMiddlewareOptions1 = {
    ...baseMiddlwwareOptions,
    appBaseUrl: 'https://first.com',
    routes: {
      login: { path: '/first-login' },
      loginCallback: { path: '/first-authorization-code/callback' }
    }
  }
  const oidcMiddleware1 = new lib1.ExpressOIDC(oidcMiddlewareOptions1)
  expressApp.use(oidcMiddleware1.router)
  
  delete require.cache[require.resolve('passport')]
  delete require.cache[require.resolve('@okta/oidc-middleware/src/oidcUtil.js')]
  delete require.cache[require.resolve('@okta/oidc-middleware/src/connectUtil.js')]
  delete require.cache[require.resolve('@okta/oidc-middleware/src/ExpressOIDC.js')]
  delete require.cache[require.resolve('@okta/oidc-middleware')]
  const lib2 = require('@okta/oidc-middleware')
  
  const oidcMiddlewareOptions2 = {
    ...baseMiddlwwareOptions,
    appBaseUrl: 'https://second.com',
    routes: {
      login: { path: '/second-login' },
      loginCallback: { path: '/second-authorization-code/callback' }
    }
  }
  const oidcMiddleware2 = new lib2.ExpressOIDC(oidcMiddlewareOptions2)
  expressApp.use(oidcMiddleware2.router)

what do you think?

@swiftone
Copy link

@LiranBri - That will allow multiple instances of the middleware, but have you confirmed that these instances aren't maintaining different fragmented sessions that will lead to a user having problems when follow-up requests end on different server instances?

@LiranBri
Copy link

LiranBri commented Mar 28, 2020

@swiftone why would it happen?
If the backend stores the user's context data in memory session on the backend side, I expect each instance to store its own data, even if they would share the same session object.

It may happen if the same user would use different domains, and the key in the session object is the user id.. but the idea, at least in my design, is that each user may use a specific domain, but should not use another

but I didn't check it because in my case, the session is stored on the client's cookie and not backend side memory (which I believe is the best practice, to keep the backend stateless).

@LiranBri
Copy link

LiranBri commented Mar 28, 2020

I see that there is an optional, undocumented, option sessionKey which controls the key in the session object. so it can be used to isolate each instance.
by default, it takes just the issuer, which is indeed shared between all instances.
it should be pretty simple to create a dedicated sessionKey for each instance, compound of issuer + appBaseUrl (or routes.login.path)

@swiftone @aarongranick-okta
is there a reason the option sessionKey not documented?
I can create a PR for documentation

@rcollette
Copy link

rcollette commented Mar 30, 2020

I have a need for this to be able to white label a single instance website for use with multiple hosts/domains.

@swiftone
Copy link

swiftone commented Apr 3, 2020

related to okta/okta-oidc-js#497

@swiftone
Copy link

Internal ref: OKTA-291513

@peterhriser
Copy link

is there any update on this? I saw there was a PR out that claimed to remediate this issue from 2019 that was never reviewed.

okta/okta-oidc-js#498

At first glance this seems ok!

@aarongranick-okta
Copy link
Contributor

@peterhriser I will take a look and see if we can move this PR forward

@rcollette
Copy link

The workaround of deleting the require cache is not an option when using es modules since es modules are immutable. This is a major blocker.

@denysoblohin-okta denysoblohin-okta transferred this issue from okta/okta-oidc-js Oct 29, 2021
@liamjobrien
Copy link

Has anyone found a workaround/solution for ES Modules? I need to support multiple IdPs, if this library does not support this any other library recommendations that do support multiple IdPs?

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

No branches or pull requests

7 participants