remix-auth-oauth2 icon indicating copy to clipboard operation
remix-auth-oauth2 copied to clipboard

How can I configure the issuer, clientId, and clientSecret at request time while still abstracting?

Open devbytyler opened this issue 2 years ago • 4 comments

Help! Been stuck on this for hours.... 😩

I have a requirement where each customer has unique credentials for issuer, clientID, and clientSecret. (Okta)

I don't know which credentials to configure on the strategy until the user tells me which tenant they are trying to log in to. At that point I can look up the tenant and grab the credentials. Therefore I can't create the strategy until after that point.

This is the problem I'm having (and it might be due to me not understanding typescript): I import my instance of the authenticator:

export const authenticator = new Authenticator<string | undefined>(storage, {
  sessionKey: 'userEmail',
});

Then in my action function I create the strategy and register it on the authenticator:

import {authenticator} from 'auth.server.tsx';

export async function action({ request }: ActionArgs) {
  const user: User = getUserFromDB();
  if (user.company.oktaClientId && user.company.oktaClientSecret && user.company.oktaDomain) {
    let oktaStrategy = new OktaStrategy(
      {
        issuer: `https://${user.company.oktaDomain}/oauth2/default`,
        clientID: user.company.oktaClientId,
        clientSecret: user.company.oktaClientSecret,
        callbackURL: `${process.env.FRONT_URL}/auth/okta/authorization-code/callback`,
      },
      async ({ accessToken, refreshToken, extraParams, profile }) => {
        // Do stuff with profile
      }
    );
    authenticator.use(oktaStrategy);
    try {
      return authenticator.authenticate('okta', request);
    } catch (error) {
      //....
    }
  }
}

As soon as I try to call authenticator, i get the error:

strategy okta not found

which leads me to think the registration isn't happening.

I HAVE gotten this all to work by defining everything inline in the action function. (i.e. creating the authenticator, creating the strategy, registering the strategy, and calling authenticate.) Seems like something about importing it from another module breaks it. While it worked, I would much rather be able to remove all the authenticator logic from my login route, including configuring the strategy.

Do I need to have some kind of singleton for my Authenticator?

Any help would be very appreciated.

devbytyler avatar Dec 01 '22 23:12 devbytyler

closing this as I found my problem was something else. Still haven't found a clean way to dynamically set the secrets at request time

devbytyler avatar Dec 02 '22 04:12 devbytyler

What I would do is to create the Authenticator and Strategy on the server and identify the tenant there, then use getLoadContext to pass it to Remix, so in your loaders you do

export async function loader({ request, context }: LoaderArgs) {
  context.auth.authenticate("okta", request, options)
  // or
  context.auth.isAuthenticated(request, options)
  // more code here
}

sergiodxa avatar Dec 02 '22 19:12 sergiodxa

Thanks for the response @sergiodxa!

Could you clarify what "create...on the server" means? Seems like I'm a bit outside the norm here so having a hard time finding examples.

Can getLoadContext() be used with the RemixAppServer? I could only find examples with an express server, and the documentation is pretty sparse.

devbytyler avatar Dec 02 '22 21:12 devbytyler

Could you clarify what "create...on the server" means? Seems like I'm a bit outside the norm here so having a hard time finding examples.

Remix is designed to work as a request handler inside an HTTP server, so you can run code inside the HTTP server itself which runs before Remix, there you have access to the request the HTTP server uses (e.g. the Express request object) so you can identify from that object the tenant.

Can getLoadContext() be used with the RemixAppServer? I could only find examples with an express server, and the documentation is pretty sparse

It can't be used with Remix App Server, the idea of RAS is to be used when you don't need to customize the HTTP server, if you want to you will have to switch to Express, you can use rmx-cli package to eject from RAS to Express, see https://github.com/kiliman/rmx-cli#-eject-ras

sergiodxa avatar Dec 05 '22 22:12 sergiodxa