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

Ability to apply different configuration values at run-time

Open kodeschooler opened this issue 1 year ago • 15 comments

How would one go about changing the acr_values in the settings object after someone is authenticated? For example, when an admin user selects a different customer from a list, I'd like to set the acr_values param in the config object to the tenant selected. The field is readOnly. It is my understanding this class is instantiated with settings once when the app loads so I'm not sure if there's a workaround (however clunky, for now) or if my understanding is incorrect. Thank you.

kodeschooler avatar Sep 01 '22 22:09 kodeschooler

By design the values received from the authz server are read-only and not intended to be altered on application side. If you need something like this you can grab the read-only values and do what you like in your application context...

pamapa avatar Sep 02 '22 06:09 pamapa

You cannot change things on the fly, you will have to reinstantiate your UserManager.

And if like you said, you need to instantiate anything once when the app loads, then you can:

  1. Save any settings you need in say sessionStorage
  2. Reload the page
  3. Read from storage -> if settings -> delete them from storage and use them

This is basically what I'm doing for the demo-app settings of my lib (which is just a wrapper around oidc-client-ts + adds mobile capabilities).

Badisi avatar Sep 02 '22 07:09 Badisi

@Badisi thanks for the quick answer. Do you have source code for your demo app and/or library? I'm using a 3rd party wrapper library in react that's based around the oidc-client library.

kodeschooler avatar Sep 05 '22 18:09 kodeschooler

@Badisi Or I should say where in your app are you doing that (i just saw that you had a link to your codebase)?

My problem is the UserManager gets instantiated at the top of my app, currently with static settings defined at the app level. If I create a new instance within a child component and call signInSilent or whatever, when it redirects back the userManager it's instantiated again and overwritten with the static settings. So I'd need to:

  1. Within the code that gets the tenant id from drop down, write to sessionStorage.
  2. Then remove user and do a silentRedirect.
  3. Then when my top-level userManager is re-instantiated, I'd read it from session storage (and then delete, I think? As long as it's not needed until the next auth- but what about silent renewals?). It seems as though I'll need to dynamically create the settings object for UserManager depending on some store (like session) and handle it for all scenarios.

I'm using React so it's harder to think about what's actually happening within the react wrapper I'm using of the oidc client library. I need to do some logging. It's just not intuitive at the moment when it does and doesn't get instantiated.

So far it's been simple and has just worked with the same settings. Alll child components have access to the user data and apis. So we're only using signIn/signOut/signInRedirect, etc.

kodeschooler avatar Sep 06 '22 02:09 kodeschooler

I'm not familiar with the react wrapper but I can see that it works exactly the same as oidc-client-ts: you need to instantiate it with some config.

So my solution remains: you can load your app with some settings found in sessionStorage (or fallback with some defaults) and later on, save some other settings and simply reload your page.

I don't know exactly what you are trying to achieve and how your app is modeled, but something along with:

// index.jsx

let oidcConfig = sessionStorage.getItem("my-oidc-config");
if (oidcConfig) {
  sessionStorage.removeItem("my-oidc-config");
} else {
  oidcConfig = {
    authority: "<your authority>",
    client_id: "<your client id>",
    redirect_uri: "<your redirect uri>",
    // acr_values
  };
}

ReactDOM.render(
  <AuthProvider {...oidcConfig}>
    <App />
  </AuthProvider>,
  document.getElementById("app")
);

Then later on :

const newOidcConfig = {
  ...manager.settings,
  // new acr_values
};
sessionStorage.setItem("my-oidc-config", JSON.stringify(newOidcConfig));
location.reload();

And maybe, depending on your needs, you may only save a partial config and not the whole one (ie. save only the acr_values).

Badisi avatar Sep 06 '22 22:09 Badisi

Thanks @Badisi. I was only planning on storing one setting but it actually might be better to simply store the entire thing. But I think I got what you're saying.

In order to get a new access token with the tenant_id from acr values, I don't know if just reload will work or if I have to say: sessionStorage.setItem(config, JSON.stringify(newConfig);

Then: userManager.removeUser() auth.signInRedirect(newConfig)

Meaning actually remove the user and send them to the token server again with the new acr_values. There still needs some backend work done before I can fully test.

Anyway, another question: when you spread the manager.settings before setting sessionStorage, I notice in the debugger that the current settings for acr_values have underscores (I assume those are read-only?). Almost all the properties of that object have underscores once instantiated. Do I need to use _acr_values when copying? If not it would put it as a new property. And either way I see you're just passing the config to the provider.

I guess I keep coming back to the idea of 'if this is read only and can only be set during instantiation, will this work?' Meaning, won't I have to say new UserManager() both when the object is in session or not. Does that make any sense?

Thank for your help

kodeschooler avatar Sep 07 '22 16:09 kodeschooler

Hi,

I've been passing additonal arc_values on the signinRedirect using oidc-client-js. which allows passing arc_values https://github.com/IdentityModel/oidc-client-js/blob/dev/src/OidcClient.js#L62

was there a reason why you changed this behaviour ?

there are use cases for this, e.g. passing a One Time Access Code to the Authorization Server.

mickdelaney avatar Sep 12 '22 13:09 mickdelaney

You can still pass arc_values in signinRedirect , there is only a type problem. Temporary you can disable that type check. To fix the type error you can make a MR, which adds arc_values to ExtraSigninRequestArgs...

pamapa avatar Sep 13 '22 11:09 pamapa

@pamapa what is a MR ?

kodeschooler avatar Sep 14 '22 01:09 kodeschooler

MR = merge request

pamapa avatar Sep 14 '22 09:09 pamapa

@Badisi I liked the idea of saving the entire settings object in storage, but I'm getting a circular reference error when trying to stringify it. For example:

Part 1:

// in App.tsx:

const oidcConfig = {
    // all your options
}

let userMgr= new UserManager(oidcConfig);

<AuthProvider userManager = { userMgr }>
     <Layout/>
 </AuthProvider>,

Part 2:

import {auth} from '...' 

const newConfig = {
   ...auth.userManager.settings,
  acr_values: 'whatever',
  prompt: 'login'
}

const result = JSON.stringify(newConfig);  // blows up with circular reference error

When you read the object it's copying a bunch of private properties (_a, _b, _c, etc) and methods, and the circular reference error occurs.

Also, in case you're wondering why I'm instantiating that class, it's because when I tried just passing an object to the provider it blew up immediately with this error from the oidc lib. ctor in SignInRequest: no client_id passed. I hardcoded the client id and saw it get called once with it but it threw an error immediately.

Does that make sense? I don't know why I have to create an instance, I thought you could just spread your settings.

kodeschooler avatar Sep 16 '22 23:09 kodeschooler

I don't think it's a good idea to save the whole setting object anyway. You should save only what you are giving to oidc-client-ts at the first place (especially if the lib is polluting the settings afterwards).

The idea is to always have the same execution workflow even after saving and reloading your app.

Let say you have 3 settings (a, b, c) that are required by oidc-client-ts:

Example 1

  1. App load -> no cache -> use default values { a, b, c }
  2. oidc-client-ts init
  3. Store { a, b, c} in cache
  4. Reload app
  5. App load -> cache found -> use cached values { a, b, c}
  6. ...

Example 2

  1. App load -> no cache -> use default values { a, b, c }
  2. oidc-client-ts init
  3. Store { a, b, c, _d, _e, f, g } in cache
  4. Reload app
  5. App load -> cache found -> use cached values { a, b, c, _d, _e, f, g }
  6. ...

In the example 2, we see that 1. and 5. are not identical in the way they are initializing oidc-client-ts. Which could lead to some error like (maybe) the ctor in SignInRequest, because in 5. the lib could react to some settings that should not be there.

Badisi avatar Sep 17 '22 07:09 Badisi

@kodeschooler, note that the idea about using the storage was just to answer the question "Ability to apply different configuration values at run-time".

If your issue is only about changing the acr_values then I would go with what @mickdelaney suggested. When you need to change it, simply call auth.signinRedirect({ acr_values }).

Badisi avatar Sep 17 '22 08:09 Badisi

@Badisi Gotcha. So far changing the acr_values during application usage is my only issue. I'll just stick with storing the tenant id based on that workflow you outlined- which makes perfect sense. And hopefully I can pass that option directly to signInRedirect. I just need to set up some tenants now to experiment with and see if the resulting access token has that tenant embedded in it for subsequent calls and that the BE works as expected. Thanks for your thoughts.

@pamapa thank you for making that change. Naïve question but I'm wondering, if I'm using the react context version that wraps this oidc-client-ts library, will they update their version to pick up this MR when approved so I can update mine? Is that normally how it works?

Thanks to everyone who commented. This project has great communication compared to another one (that one isn't actively worked on and it recommended this one).

kodeschooler avatar Sep 19 '22 20:09 kodeschooler

@pamapa thank you for making that change. Naïve question but I'm wondering, if I'm using the react context version that wraps this oidc-client-ts library, will they update their version to pick up this MR when approved so I can update mine? Is that normally how it works?

If you talk about react-oidc-context, this library has a peer dependency to this library. If you pick up the next version of this library, i guess it will just work.

pamapa avatar Sep 20 '22 07:09 pamapa