maxmantz/redux-oidc

Silent renew reloads entire page

trollr opened this issue · 2 comments

Got some weird issue I can't explain myself.

I reduced the lifetime of the token to 60 seconds. The refresh triggers as expected but on each silent renew I get a new local Storage entry and a full page reload.

AppRenderer.js

ReactDOM.render(
  <Provider store={store}>
    <OidcProvider store={store} userManager={userManager}>
      <App/>
    </OidcProvider>
  </Provider>,
  document.getElementById('root'),
);

serviceWorker.unregister();

App.js

class App extends Component {
  componentWillMount() {
    const direction = getDirection();
    if (direction.isRtl) {
      document.body.classList.add('rtl');
      document.body.classList.remove('ltr');
    } else {
      document.body.classList.add('ltr');
      document.body.classList.remove('rtl');
    }
  }

  render() {
    const { user, locale } = this.props;
    const currentAppLocale = AppLocale[locale];

    return (
      <div className="h-100">
        <IntlProvider
          locale={currentAppLocale.locale}
          messages={currentAppLocale.messages}>
          <React.Fragment>
            <NotificationContainer/>
            {isMultiColorActive && <ColorSwitcher/>}
            <Router>
              <Switch>
                <AuthRoute path="/app" user={user} component={app}/>
                <Route path="/callback" component={callback}/>
                <Route path="/error" exact component={error}/>
                <Route path="/" exact component={main}/>
                <Redirect to="/error"/>
              </Switch>
            </Router>
          </React.Fragment>
        </IntlProvider>
      </div>
    );
  }
}

const mapStateToProps = ({ oidc, settings }) => {
  const user = oidc.user;
  const { locale } = settings;
  return { user, locale };
};

function mapActionsToProps(dispatch) {
  return {
    dispatch,
  };
}

export default connect(
  mapStateToProps,
  mapActionsToProps,
)(App);

AuthRoute.js

import { Route } from 'react-router-dom';
import React from 'react';
import userManager from '../../helpers/UserManager';

class AuthRoute extends Route {

  render() {
    const { component: Component, user, ...rest } = this.props;

    if (!user || user.expired) {
      userManager.signinRedirect();
      return (
        <div>Redirecting...</div>
      );
    } else {
      return (<Route
        {...rest}
        render={props =>
          <Component {...props} />
        }
      />);
    }
  }
}

export default AuthRoute;

store.js

const sagaMiddleware = createSagaMiddleware();

const middlewares = [sagaMiddleware];

export function configureStore(initialState) {

  const store = createStore(
    reducers,
    initialState,
    compose(applyMiddleware(...middlewares)),
  );

  sagaMiddleware.run(sagas);

  if (module.hot) {
    module.hot.accept('./reducers', () => {
      const nextRootReducer = require('./reducers');
      store.replaceReducer(nextRootReducer);
    });
  }

  return store;
}

const store = configureStore();
loadUser(store, userManager);
export default store;

UserManager.js

import { createUserManager } from 'redux-oidc';

const userManagerConfig = {
  client_id: 'xxxxxx',
  redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
  response_type: 'token id_token',
  scope: 'openid',
  authority: 'https://local',
  silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew/silent_renew.html`,
  automaticSilentRenew: true,
  filterProtocolClaims: true,
  loadUserInfo: true,
};

const userManager = createUserManager(userManagerConfig);
export default userManager;

It seems, that the user is not fully loaded while hitting the AuthRoute. However I don't understand why I get multiple local Storage entries and why the user is not fully loaded :( In my opinion the example app and my app are not that different at all.

Would be great if you have any hints :)

Silent renewal is usually triggered 5 minutes before the expiry time has been reached. Setting the token lifetime to 60 seconds will inevitably set up an endless loop of silent renewals, which might explain that behavior.

In addition, your AuthRoute triggers signinRedirect() without any user interaction. The initial state of the oidcReducer has the user at null which means that for the first render cycle, the condition will always be true. Please consider using a dedicated login button where the user actively sets the auth flow in motion. It sometimes just takes a split second for the user to appear.

I don't know whether or not silent renewal is responsible for the rerendering of the whole page, but you could enable logging to see what the output shows and debug it further

Oh found the issue while debugging. The Iframe received X-Frame-Options: deny header.

For everyone who wants to enable logging:

import { Log } from 'oidc-client';

Log.logger = console;
Log.level = Log.DEBUG;