https://supertokens.com/ logo
Title
n

n1ru4l

10/24/2022, 8:29 AM
Hey πŸ‘‹ I am trying to implement multi tenancy (for our cloud implementation; single instance for all customers). This is the implementation I have in mind: 1. An organisation entity is linked to an OpenID Connect provider 2. A link is generated for logging in via a specific OpenID Connect provider (
https://example.com/auth/org?id=oidc_provider_id
) 3. People visiting this link start the OAuth2 OIDC login flow with the organization specific OIDC provider 4. Users that newly register/log in are automatically added to the specific organization I am now trying to figure out how to handle this on the SuperTokens Next.js side. Within the
getServerSideProps
function of the
auth
route I can load the specific integration (id, secret and issuer url) from the database. However, it is unclear to me on how I would then utilize this information with the SuperTokens SDK (since it is a singleton that is initialized once and cannot be initialised per request πŸ€” . Do you have any pointers here?
r

rp

10/24/2022, 8:40 AM
hey @n1ru4l
so essentially, on the frontend, you want to set the provider list in the thirdparty recipe based on the
id
in the query params.
On the backend, you can initialise all of the providers and dynamically load their config during API calls. You want to override the signinup and getauthorisationurl API on the backend to fetch the config of the tenantID from your db. You can get the teantId from the request object somehow. Then set the config in the the userContext before calling the original implementation. This userContext with the config will be available in your custom provider's definition (in the
get
function)
n

n1ru4l

10/24/2022, 8:45 AM
> On the backend, you can initialise all of the providers and dynamically load their config during API calls. Since providers are added/removed dynamically and we can potentially have hundreds that does not seem like a nice solution πŸ€”
oh wait i misunderstood
okay let me try this
r

rp

10/24/2022, 8:46 AM
Ok
I could show it to you on a call sometime tomorrow or on Thursday
n

n1ru4l

10/24/2022, 8:47 AM
let me try how far I get today - if not lets schedule a call πŸ™‚
r

rp

10/24/2022, 8:47 AM
Sounds good
n

n1ru4l

10/24/2022, 8:47 AM
thank you ❀️
Okay it seems like my
authorisationUrlGET
override is not used at all πŸ€” I added a simple override with a console.log, and it never shows up The HTTP call (initiated by supertokens react) to
http://localhost:3000/api/auth/authorisationurl?thirdPartyId=org%7Ckekeke
simply gives me
{"message":"The third party provider org|kekeke seems to be missing from the backend configs."}
const getOIDCOverrides = () => {
  console.log('it is applied for sure');
  const override: ThirdPartEmailPasswordTypeInput['override'] = {
    apis(originalImplementation) {
      return {
        ...originalImplementation,
        async authorisationUrlGET(input) {
          console.log(input);

          return originalImplementation.authorisationUrlGET!(input);
        },
      };
    },
  };

  return override;
};
I added a console.log for the
apis
function - it seems like that one is not invoked
r

rp

10/24/2022, 9:52 AM
Oh yea. You need to add a provider with that id in the provider list in the backend for it to work
n

n1ru4l

10/24/2022, 9:54 AM
πŸ€”
Any workaround if we don't know the list of oidc providers upfront? πŸ˜„
r

rp

10/24/2022, 9:55 AM
Hmm. On the backend you would need to add all the ones that your app can integrate with
n

n1ru4l

10/24/2022, 9:55 AM
Uhmm, but we generate the id on the fly as an user adds an integration to the org
r

rp

10/24/2022, 9:57 AM
Hmm I see. But how will the SDK know how to integrate with that org without you specifying info for it - like the auth exchange url, or how to get the profile info from that org
n

n1ru4l

10/24/2022, 9:57 AM
it will load it from the db
we store client id, secret (encrypted) and issuer url in the db
r

rp

10/24/2022, 9:59 AM
Yea that’s fine. But the actual integration with that info is on a per provider basis. For example, the way you get the user profile from Microsoft AD is different compared to something like LinkedIn
n

n1ru4l

10/24/2022, 9:59 AM
The userinfo is received via the OpenID Connect UserInfo endpoint
r

rp

10/24/2022, 10:00 AM
You would also need to store the mapping of id token to user info then. Like which field in the id token corresponds to the email (for example)
n

n1ru4l

10/24/2022, 10:01 AM
Yeah, but that is not really related to this issue, no? It is something that must also be solved for sure - but I cannot get to this point yet
r

rp

10/24/2022, 10:02 AM
Right yea. Ok so I have an idea
Essentially, you create one generic open id client, which some is like β€œgeneric”
On the frontend and backend
And then, on the backend, you not only load the client id, client secret etc config, but also the auth urls for that provider from the db
And then dynamically inject all of those in the generic provider on the backend
Including info about how to get profile info from the id token
n

n1ru4l

10/24/2022, 10:10 AM
Oh so the organization specifc auth id would be an additional query parameter? πŸ€”
r

rp

10/24/2022, 10:10 AM
Yea. A custom param in the request
n

n1ru4l

10/24/2022, 10:10 AM
export const startAuthFlowForOIDCProvider = async (oidcId: string) => {
  let authUrl = await getAuthorisationURLWithQueryParamsAndSetState({
    providerId: 'org',
    authorisationURL: `${env.appBaseUrl}/auth/callback/org`,
  });

  const url = new URL(authUrl);
  url.searchParams.set('oidc_id', oidcId);

  authUrl = url.toString();

  window.location.assign(authUrl);
};
Is it possible to add a query parameter to the callback url?
or will the oidc provider complain (as it also adds query parameters)
r

rp

10/24/2022, 10:14 AM
You want to add info to the state in this case.
What’s the use case of adding extra info?
n

n1ru4l

10/24/2022, 10:15 AM
oh so org is the generic provider - but also we need to know which org it is, right? So that is why we also need to somehow pass the open id connect provider id
r

rp

10/24/2022, 10:16 AM
I see. When handling the callback. Makes sense!
Oh and you may also want to modify the value of thirdpartyid in the signinup function to add the actual provider id to the input value of thirdpartyid so that in the db, users are not all shared for the same thirdpartyid
n

n1ru4l

10/24/2022, 12:30 PM
@rp Okay so the biggest question really is how I could provide/inject the organization specific id into all steps in a "secure" way that would not allow an attacker to inject a random one
Into
thirdPartySignInUpPOST
and
authorisationUrlGET
to be precise
for
authorisationUrlGET
just relying on the
referer
headers seems to be safe enough...
but for
thirdPartySignInUpPOST
I need a safe way πŸ€”
r

rp

10/24/2022, 12:43 PM
you can use the state. Also, even if an attacker injects a different org specific ID in
thirdPartySignInUpPOST
, it won't work cause the auth code exchange will fail
n

n1ru4l

10/24/2022, 1:29 PM
Potentially stupid question... Where do i access the state πŸ˜“
r

rp

10/24/2022, 1:30 PM
Well yea. You will need to add that as a custom prop in the request body from the frontend and access it on the backend
n

n1ru4l

10/24/2022, 1:32 PM
authorisationRedirect: {
  // this contains info about forming the authorisation redirect URL without the state params and without the redirect_uri param
  url: `${oidcConfig.domain}/authorize`,
  params: {
    client_id: oidcConfig.clientId,
    scope: 'openid email',
    response_type: 'code',
    redirect_uri: `${env.appBaseUrl}/auth/callback/oidc`,
    state: oidcConfig.id,
  },
},
so here i pass the state to the oicd provider (within a custom TypeProvider) on the backend
how does okta/any other oidc provider give me back that state?
r

rp

10/24/2022, 1:33 PM
Query param in the callback URL
n

n1ru4l

10/24/2022, 1:39 PM
Does SuperTokens by default add a state query parameter?
Seems like I would need a way of overriding
generateStateToSendToOAuthProvider
figured it out
I think there is a bug with the
getAuthorisationURLFromBackend
function override
options
.
getAuthorisationURLFromBackend(input) {
  const maybeId: unknown = input.userContext['oidcId'];

  if (typeof maybeId === 'string') {
    return originalImplementation.getAuthorisationURLFromBackend({
      ...input,
      options: {
        preAPIHook: async options => {
          alert('NANI');

          const url = new URL(options.url);
          url.searchParams.append('oidc_id', maybeId);

          return {
            ...options,
            url: url.toString(),
          };
        },
      },
    });
  }
  return originalImplementation.getAuthorisationURLFromBackend(input);
The
preAPIHook
seems to never be invoked
r

rp

10/24/2022, 3:14 PM
@nkshah2 can have a look at this sometime tomorrow
n

n1ru4l

10/24/2022, 3:32 PM
worked around this by instead having a global
preAPIHook
override
r

rp

10/24/2022, 3:36 PM
Thanks. @nkshah2 can have a look at this to see if there is a bug in the SDK
@rp Thank you so much for all the help today! 😊
r

rp

10/24/2022, 3:38 PM
Happy to help :)! Thank you as well for giving us insight into what needs improving
n

nkshah2

10/24/2022, 5:30 PM
Hi @n1ru4l can I see how you are using
getAuthorisationURLFromBackend
. I tried setting up a sample project and the pre api hook works correctly for me
n

n1ru4l

10/25/2022, 9:10 AM
@nkshah2 Did you test it on Next.js?
could this be related to a dependency of supertokens being resolved to the wrong version?
n

nkshah2

10/25/2022, 9:50 AM
Ill setup a sample app to test with NestJS, in the meantime what version of supertokens-web-js are you using?
I have a minimal reproduction
I call
getAuthorisationURLWithQueryParamsAndSetState
n

nkshah2

10/25/2022, 9:50 AM
Ah thanks for that ill have a look
n

n1ru4l

10/25/2022, 9:50 AM
It is not next.js related it seems
n

nkshah2

10/25/2022, 10:48 AM
Right I can reproduce the bug and ill be working on a fix for this, if you like you can open an issue about this in
supertokens-auth-react
to keep track of progress on this
And yeah its not NextJS specific
n

n1ru4l

10/26/2022, 7:35 AM