react-oidc-context
react-oidc-context copied to clipboard
Callback for loading existing credentials?
(This can be either documentation request or a new feature request, I am not sure)
My react app uses Redux to store user info, and I am trying to set it from the addUserLoaded
callback and from onSigninCallback
config callback. It seems to work , but only during initial signin, when no valid credentials are present.
When I reload the application, neither of those callbacks appear to be triggered. It goes straight to the authenticated state.
Is there another or more universal callback available which can be used to "catch" when user credentials are either loaded or found in the local storage? I want to avoid trying to deduce it from useAuth().user
. In the past we used Amplify and did period checks for this but it seems hacky and now impossible to do without direct access to UserManager
(I saw https://github.com/authts/react-oidc-context/issues/331).
My callback in the AuthProvider
config:
const oidcStateStore = new oidcClient.WebStorageStateStore({
store: window.localStorage,
});
const oidcConfig: OidcAuthProviderProps = {
authority: 'https://...',
client_id: 'client1',
redirect_uri: window.location.origin,
loadUserInfo: true,
userStore: oidcStateStore,
automaticSilentRenew: false,
onSigninCallback: (user: oidcClient.User | void): void => {
window.history.replaceState(
{},
document.title,
window.location.pathname,
);
// set user info in Redux store
},
};
My addUserLoaded
callback:
const auth = useAuth();
useEffect(() => {
return auth.events.addUserLoaded((user: User) => {
// set user info in Redux store
});
}, [auth.events]);
Behind useAuth()
is a reducer. You can directly trigger on auth.user
in your useEffect
. See https://github.com/authts/react-oidc-context/blob/main/src/AuthProvider.tsx#L175, this is doing already what you need...
Triggering on auth.user
does not seem work either:
useEffect(() => {
return auth.events.addUserLoaded((user: User) => {
// never called for subsequent page refresh
});
}, [auth.user]);
I've added logging statement in reducer()
and I can see that it's only called with INITIALIZED
action type and never with USER_LOADED
in this case.
I even tried making my own manager and hook into addUserLoaded()
there - still does not get triggered.
class CustomUserManager extends oidcClient.UserManager {
constructor(settings: oidcClient.UserManagerSettings) {
super(settings);
this.events.addUserLoaded((user) => {
console.info(`does not happen`);
});
}
}
const oidcConfig: OidcAuthProviderProps = {
...
implementation: CustomUserManager,
...
}
It looks like the culprit may be here: https://github.com/authts/react-oidc-context/blob/main/src/AuthProvider.tsx#L160 it calls userManager.getUser()
which tries to load from the storage and in this particular case it suppresses callback notifications (false
passed as the second argument to _events.load()
): https://github.com/authts/oidc-client-ts/blob/e735d83454f7abbefeeb4aaab899647b8655c74e/src/UserManager.ts#L124
In fact, it looks like UserManager triggers "user loaded" callback only from _signInEnd()
, _useRefreshToken()
and _remokeInternal()
- none of which are being triggered if user credentials already exist in the storage.
I managed to make it work by implementing overriding the default getUser()
behavior but it seems hacky as hell:
class CustomUserManager extends oidcClient.UserManager {
private _user: oidcClient.User | null;
constructor(settings: oidcClient.UserManagerSettings) {
super(settings);
this._user = null;
}
// Follows the parent implementation except forces raising
// "load user" events when user is loaded for the first time.
public async getUser(): Promise<oidcClient.User | null> {
const logger = this._logger.create('getUser');
const user = await this._loadUser();
if (user) {
logger.info('user loaded');
let raiseEvents = false;
if (this._user === null) {
this._user = user;
raiseEvents = true;
// set user info in Redux store
}
this._events.load(user, raiseEvents);
return user;
}
this._user = null;
logger.info('user not found in storage');
return null;
}
}
Do you see other / better way to do it?
In order to initiate the auth process you need to call signinRedirect
somewhere in your code...
Yes, I do that below, in the same component where I call useAuth()
/useEffect()
:
if (auth.isAuthenticated) {
return <>{children}</>;
}
if (!auth.error && auth.activeNavigator === undefined && !auth.isLoading) {
console.info('Initiating signinRedirect()');
auth.signinRedirect();
}
// show "authenticating..." spinner or an error message, depending on
// auth.error and auth.isLoading...
It is triggered only during the initial load. Once the access tokens are in the local storage, signinRedirect()
is not called - and I don't expect it to.
@alexandroid , did you ever managed to find a more elegant way to do this instead of overriding the getUser
?
@alolis No =\ I am not sure why this issue was closed.