oidc-client-ts icon indicating copy to clipboard operation
oidc-client-ts copied to clipboard

Interoperability Between OidcClient and UserManager

Open chrstophr-wltrs opened this issue 3 years ago • 0 comments

Hi all! I'm adapting a testing suite from one framework to another. One of the major reasons for this switch was the fact that the new framework supports the use of await. The previous code used the OidcClient class, because apparently the app under test and the authentication system it uses has some eccentricities. Problem is, I don't know how many of those "eccentricities" can be handled by the UserManager class, rather than having to be handled automatically.

However, I'd really like to use UserManager.startSilentRenew() if possible, because that will make juggling multiple users between multiple worker processes a little easier as the suite of tests grows and users therefore need to remain authenticated for longer periods of time.

While I don't think it should be necessary for the question at hand, I've included some of my example code that I'm hoping to adapt to utilize UserManager. Just assume that there are functions get() and post() for making HTTP requests, and that the response bodies are always parseable as JSON. I know the code block is a little long, I couldn't find a way to force a fixed-height, so that it would take less space and allow one to scroll through it.

interface IOktaConfiguration {
  authorizeUrl: string;
  clientId: string;
  redirectUrl: string;
  tokenUrl: string;
}

interface IRequest {
  username: string,
  password: string,
  state: string,
  nonce: string
}

const OIDC = "https://betaidentity.company.com";
const { SSO } = global.config();
// example SSO url: "https://sso.uat.company.com/api"
const baseUrl = "https://test.product.com/app/";

const signInRequest = new OidcClient({
    authority: OIDC,
    client_id: "app",
    redirect_uri: `${baseUrl}/auth-callback`,
    post_logout_redirect_uri: `${baseUrl}/log-out`,
    response_type: "id_token token",
    scope: "openid prod_services_api",
  }).createSigninRequest({
    data: {
      pathname: "/sign-in",
      search: "",
    }
  });

const request: IRequest = {
  username: "[email protected]",
  password: "AV3rySecureP@ssword",
  state: signInRequest.state._id,
  nonce: signInRequest.state._nonce,
};

async function login() {
  
  const csrfToken = await get(`${OIDC}/ui/login`).headers["set-cookie"][
    "XSRF-TOKEN"
  ];

  if (SSO) {
    const { code_challenge, code_verifier } = pkce();
    const sessionToken = await post(`${SSO}/v1/authn`, {
      data: {
        username: request.username,
        password: request.password,
      },
    }).body.sessionToken;
    const config: IOktaConfiguration = await get(`${OIDC}/interaction/config`)
      .body.oktaConfiguration;
    const code = await get(config.authorizeUrl, {
      params: {
        code_challenge,
        sessionToken,
        client_id: config.clientId,
        code_challenge_method: "S256",
        nonce: request.nonce,
        prompt: "none",
        redirect_uri: config.redirectUrl,
        response_mode: "okta_post_message",
        response_type: "code",
        state: request.state,
        scope: "openid email",
      },
    }).body.match(/data\.code = '([^']+)'/m)[1];

    const tokens = post(config.tokenUrl, {
      form: {
        code,
        code_verifier,
        client_id: config.clientId,
        redirect_uri: config.redirectUrl,
        grant_type: "authorization_code",
      },
    }).then(({ body }) => {
      return {
        idToken: body.id_token,
        accessToken: body.access_token,
      };
    });

    await post(`${OIDC}/interaction/login-okta-user`, {
      headers: {
        "X-XSRF-TOKEN": csrfToken,
      },
      data: {
        tokens,
        emailAddress: request.username,
        returnUrl: "connect/authorize/callback",
      },
    });
  } else {
    await post(`${OIDC}/interaction/login`, {
      data: {
        username: request.username,
        password: request.password,
        returnUrl: "/connect/authorize/callback?client_id=app",
      },
      headers: {
        "X-XSRF-TOKEN": csrfToken,
      },
    });
  }

  return await get(`${OIDC}/connect/authorize/callback`, {
    followRedirect: false,
    params: {
      client_id: "app",
      redirect_uri: `${baseUrl}/auth-callback`,
      response_type: "id_token token",
      scope: "openid prod_services_api prod.services.app.api",
      state: request.state,
      nonce: request.nonce,
    },
  });
}

chrstophr-wltrs avatar Jul 25 '22 18:07 chrstophr-wltrs