amplify-js: Documentation does not specify how to handle return from successful hosted UI authentication

Given a generic npx create-react-app . ; npm install aws-amplify-react app with the following App.js:

import React, { Component } from 'react';
import Amplify, { Auth } from 'aws-amplify';
import { withOAuth } from 'aws-amplify-react';

const awsconfig = {
  Auth: {
    identityPoolId: ***redacted***,
    region: ***redacted***,
    userPoolId: ***redacted***,
    userPoolWebClientId: ***redacted***,
    oauth: {
      domain: ***redacted***,
      scope: ['email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
      redirectSignIn: 'http://localhost:3000/',
      redirectSignOut: 'http://localhost:3000/',
      responseType: 'code',
    },
  },
}

Amplify.configure(awsconfig)

class App extends Component {
  state = { isAuthenticated: false, }

  async componentDidMount () {
    try {
      const currentAuthenticatedUser = await Auth.currentAuthenticatedUser()

      if (currentAuthenticatedUser) { this.setState({isAuthenticated: true}) }
      else { this.setState({isAuthenticated: false}) }
    }
    catch (error) { this.setState({isAuthenticated: false}) }
  }

  render () {
    let response = (
      <button onClick={this.props.OAuthSignIn}>Log In</button>
    )

    if (this.state.isAuthenticated) {
      response = (
		<p>Logged In</p>
      )
    }

    return (
      <div className="App">
        {response}
      </div>
    )
  }
}

export default withOAuth(App)

Authentication is successful and the hosted UI redirects back to http://localhost:3000/?code=...

At this point, however, the Log In button is still displayed and isAuthenticated is still false, although the call to Auth.currentAuthenticatedUser() has been successful and the access, id and refresh tokens have been added to local storage.

Reloading the page changes the state of isAuthenticated to true and everything works as expected, although the ?code=... query parameter is still present in the url.

It would be of great help if the documentation provided a simple example like this illustrating the following:

  • where and how in the lifecycle to handle the redirect from a successful hosted UI authentication without manually reloading
  • how to strip the code query parameter without a manual redirect

I’m wondering also if there is some complete other aspect of this missing - like having to also listen to the auth events from Hub and then respond (btw the documentation on Hub seems extremely sparse)

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 18
  • Comments: 28 (6 by maintainers)

Most upvoted comments

@elorzafe I have the exact same problem! Been trying to find a solution for days now.

@powerful23 thanks for this - it does work - a few comments:

  • to remove the ?code... from the url I added a this.props.history.push('/') in checkUser() although this seems like a hack
  • there is a noticeable pause sometimes between the redirect and the auth event firing - this means that when a user is redirected back from a successful authentication, they will briefly see the “Log In” button. Because there is no state preserved from when the app originals redirects the user to the hosted UI, there is no way to set some kind of state like isAuthenticating so that we can load a spinner or something instead.

I’m fairly new to React so it is quite possible I’m missing some obvious ways of doing things, but I would think if the Amplify React components were a bit more complete in dealing with these kind of things people could get up and running w/ Cognito much quicker (not insulting the team’s efforts at all, just an opinion on which way to go with things!).

We are having the same problem where our users see our login UI for a few seconds when they are redirected back from Cognito Hosted UI. @powerful23 Do you have any timeline about when the isAuthenticating event will be implemented?

@tunecrew I can answer the second question. I agree to add a state like isAuthenticating. For example, we can dispatch an isAuthenticating event by the Hub module so you can check it every time the page is reloaded. We are also working on to add a loading page for aws-amplify-react.

@jadbox How are you redirecting your app? The code returned by Cognito will be used to revoke another call to get you the tokens for Auth so if you redirect your app before this process gets completed then you won’t be signed in successfully.

@jadbox you can do import { Hub } from '@aws-amplify/core

@tunecrew Hi, I can answer your first question. You can use the Hub category to listen on the signIn event. For exxample:

import { Auth, Hub } from 'aws-amplify';

class myComp extends Component {
     componentDidMount() {
          Hub.listen('auth', this);
     }

     onHubCapsule(capsule) {
          const { channel, payload, source } = capsule;
          if (channel === 'auth' && payload.event === 'signIn') { 
            this.checkUser();
         }
     }

     checkUser() {
           Auth.currentAuthenticatedUser().then(() => {
           });
     }
}

@uclaeamsavino thanks for you feedback. The reason why you need to listen on Hub events before calling Auth.currentAuthenticatedUser() is that there is some async process happens when redirecting back from the Hosted UI Page. We want to make sure all those async processes get done before people asking whether they are logged in or not.

The Hub module is just a very simple implementation for the local events subscription. We will improve that category in the future for more generic use. Unfortunately for now it doesn’t provide the method to unsubscribe the listener. Will create a story for that.

@jadbox I’ll try and go a gist w/ my code.

@powerful23 thanks for this - it does work - a few comments:

  • to remove the ?code... from the url I added a this.props.history.push('/') in checkUser() although this seems like a hack
  • there is a noticeable pause sometimes between the redirect and the auth event firing - this means that when a user is redirected back from a successful authentication, they will briefly see the “Log In” button. Because there is no state preserved from when the app originals redirects the user to the hosted UI, there is no way to set some kind of state like isAuthenticating so that we can load a spinner or something instead.

I’m fairly new to React so it is quite possible I’m missing some obvious ways of doing things, but I would think if the Amplify React components were a bit more complete in dealing with these kind of things people could get up and running w/ Cognito much quicker (not insulting the team’s efforts at all, just an opinion on which way to go with things!).

I am facing this exact same problem. Is there a way around it ?

@powerful23 thanks that works… Hub doesn’t have a Typescript definition on the core module, which made it non-transparent. GOOD NEWS though, your example of waiting on the Hub event first allows me to then call currentAuthenticatedUser at the right time to get the authenticated user. I’ve got a couple thoughts though:

  • This needs to be documented under Hosted UI for anyone to be successful
  • currentAuthenticatedUser should work correctly without having to magically wait for this Hub event
  • What is this Hub module and why is it used in this fashion for oauth/hosted-UI
  • at this point of Auth.currentAuthenticatedUser().then(...), should I be able to now call mobilehub services?
Hub.listen('auth', {
    onHubCapsule: (capsule:any) => {
        const { channel, payload } = capsule; // source
        if (channel === 'auth' && payload.event === 'signIn') { 
            Auth.currentAuthenticatedUser().then((data) => {
                console.log('---', data) // THIS WORKS for Hosted UI!
            });
       }
   }
});

I have one issue with this method. If I use it on my home page, then a user signs in later through a different page. The home page listener still fires and I get the warning about component not mounted:

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in Home (created by _class)
    in _class (created by Route)
    in Route (at src/index.js:52)

Does anyone know how I can unsubscribe to this when my Home component unmounts? Thanks a lot.