Amplify-js: Auth: Delay of Hub events using federatedSignIn

Created on 3 May 2019  路  4Comments  路  Source: aws-amplify/amplify-js

I am referring to the full react sample code from this section of the docs:

// App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import OAuthButton from './OAuthButton';
import Amplify, {Auth, Hub} from 'aws-amplify';
import awsmobile from './aws-exports'; // your Amplify configuration

// your Cognito Hosted UI configuration
const oauth = {
  domain: 'your_cognito_domain',
  scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
  redirectSignIn: 'http://localhost:3000/',
  redirectSignOut: 'http://localhost:3000/',
  responseType: 'code' // or token
};

Amplify.configure(awsmobile);
Auth.configure({ oauth });

class App extends Component {
  constructor(props) {
    super(props);
    this.signOut = this.signOut.bind(this);
    // let the Hub module listen on Auth events
    Hub.listen('auth', (data) => {
        switch (data.payload.event) {
            case 'signIn':
                this.setState({authState: 'signedIn'});
                this.setState({authData: data.payload.data});
                break;
            case 'signIn_failure':
                this.setState({authState: 'signIn'});
                this.setState({authData: null});
                this.setState({authError: data.payload.data});
                break;
            default:
                break;
        }
    });
    this.state = {
      authState: 'loading',
      authData: null,
      authError: null
    }
  }

  componentDidMount() {
    console.log('on component mount');
    // check the current user when the App component is loaded
    Auth.currentAuthenticatedUser().then(user => {
      console.log(user);
      this.setState({authState: 'signedIn'});
    }).catch(e => {
      console.log(e);
      this.setState({authState: 'signIn'});
    });
  }

  signOut() {
    Auth.signOut().then(() => {
      this.setState({authState: 'signIn'});
    }).catch(e => {
      console.log(e);
    });
  }

  render() {
    const { authState } = this.state;
    return (
      <div className="App">
        {authState === 'loading' && (<div>loading...</div>)}
        {authState === 'signIn' && <OAuthButton/>}
        {authState === 'signedIn' && <button onClick={this.signOut}>Sign out</button>}
      </div>
    );
  }
}

export default App;

// OAuthButton.js
import { withOAuth } from 'aws-amplify-react';
import React, { Component } from 'react';

class OAuthButton extends React.Component {
  render() {
    return (
      <button onClick={this.props.OAuthSignIn}>
        Sign in with AWS
      </button>
    )
  }
}

export default withOAuth(OAuthButton);

I found out during testing that this example is a bit misleading. I'm pretty sure the loading state could just as well be omitted as it cannot possibly affect the UI. After the redirect from the Cognito Hosted UI, there is a delay of a couple of 100ms until the Hub listener receives the signIn event. However, the Auth.currentAuthenticatedUser in the componentDidMount will immediately fire and set the authState to signIn. Until this state change in componentDidMount is finished, React will block any UI rendering, so the "loading..." div cannot possible render.

This causes a glitchy UI in the app because after returning from the Hosted UI it will initially render as signed out and then when the Hub event fires it will update based on the state change to signedIn. I am currently "solving" this with a setTimeout hack:

    Auth.currentAuthenticatedUser().then(user => {
      console.log(user);
      this.setState({authState: 'signedIn'});
    }).catch(e => {
      console.log(e);
      setTimeout(() => {
        if (this.state.authState === 'loading') {
          this.setState({authState: 'signIn'});
        }
      }, 500)
    });

This is how I make sure that the Hub event has fired before rendering anything. But this is adding the 500ms in other scenarios as well when it's not necessary. Is there a better solution?

Auth bug

Most helpful comment

This will be fixed by #3258

All 4 comments

@tinymarsracing we will work on this issue and also update the sample doc.

This will be fixed by #3258

So, I'm not sure how this is supposed to be fixed now. But maybe I'm just missing something.

@powerful23 @manueliglesias Could you point out how I'd have to change my "hacky" code from above:

    Auth.currentAuthenticatedUser().then(user => {
      console.log(user);
      this.setState({authState: 'signedIn'});
    }).catch(e => {
      console.log(e);
      setTimeout(() => {
        if (this.state.authState === 'loading') {
          this.setState({authState: 'signIn'});
        }
      }, 500)
    });

If I leave out the setTimeout like this...

  componentDidMount() {
    console.log('on component mount');
    // check the current user when the App component is loaded
    Auth.currentAuthenticatedUser().then(user => {
      console.log(user);
      this.setState({authState: 'signedIn'});
    }).catch(e => {
      console.log(e);
      this.setState({authState: 'signIn'});
    });
  }

...then I still get the old behaviour. I copied the latter sample straight from the current docs ("full react sample"), so if you documented the new behaviour somewhere else, please let me know where.

If I understand the PR you linked correctly, I would expect that you changed how currentAuthenticatedUser works under the hood in such a way that it holds resolving its promise while waiting for the hub event after coming back from the Hosted UI. I updated to the current version (1.1.34) and it doesn't do that yet. Or is this new functionality just not released yet?

I would appreciate if you could clarify these things.

Follow-up: #3841

Was this page helpful?
0 / 5 - 0 ratings

Related issues

epicfaace picture epicfaace  路  3Comments

rygo6 picture rygo6  路  3Comments

ddemoll picture ddemoll  路  3Comments

callmekatootie picture callmekatootie  路  3Comments

romainquellec picture romainquellec  路  3Comments