如何将Cognito托管的UI集成到react应用程序中?

时间:2019-07-30 10:50:28

标签: javascript reactjs amazon-cognito aws-amplify

我正在创建一个React应用-使用create-react-app和amplify-并且我正在尝试设置身份验证。我似乎无法使用托管的UI处理联合登录。

有些页面不需要身份验证即可到达,有些页面则需要用户登录。我想使用托管的UI,因为它是预先构建的。我一直在关注入门文档:https://aws-amplify.github.io/docs/js/authentication

对于背景,我具有以下组件: -Amplify-一个放大客户端,将调用包装在诸如doSignIn,doSignOut等方法中。该想法是将所有这些代码都放在一个地方。这是一个普通的javascript类 -会话-提供身份验证上下文作为React上下文。使用放大客户端设置此上下文。它具有使用上下文的HOC -页面-包含在会话HOC withAuthentication中的页面,仅当用户登录后才呈现页面

此结构实际上来自Firebase教程:https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/ 也许使用Amplify只是不可行?尽管看起来对我来说足够相似,它应该可以工作。基本思想是,会话提供单个身份验证上下文,可以使用withAuthentication HOC进行订阅。这样,只要用户登录,任何需要用户的组件都将被呈现。

最初,我将整个App组件包装在了文档描述的amplify提供的withAuthenticator HOC中。但是,这意味着未经身份验证就无法访问任何页面-需要在没有帐户的情况下访问主页。

接下来,我尝试使用登录按钮调用托管UI,然后处理响应。问题在于,托管的UI已经登录了用户,然后将其重定向回应用程序,导致其重新加载-这对于单页应用程序而言并不理想。

然后,我尝试在每次应用启动时检查用户是否已通过身份验证-以处理重定向-但这变得很混乱,因为我需要将许多放大的客户端代码移到Session上下文中以便可以正确初始化。我看到的唯一方法是使用集线器模块:https://aws-amplify.github.io/docs/js/hub#listening-authentication-events的缺点是,登录后,该应用程序将刷新,并且仍有一段时间您注销时,这会使用户体验很奇怪。 >

我本以为会有一种不引起应用程序刷新的方法。也许使用托管UI不可能。对我来说,令人困惑的是文档没有在任何地方提及它。实际上,有关于处理来自托管UI的回调的文档,据我所知,因为整个页面都会刷新,所以回调永远不会运行。

我尝试将其缩小为所需的大小。我可以根据要求提供更多。

放大:

import Amplify, { Auth } from 'aws-amplify';
import awsconfig from '../../aws-exports'; 
import { AuthUserContext } from '../Session';

class AmplifyClient {
    constructor() {
        Amplify.configure(awsconfig);
        this.authUserChangeListeners = [];
    }

    authUserChangeHandler(listener) {
        this.authUserChangeListeners.push(listener);
    }

    doSignIn() {
        Auth.federatedSignIn()
            .then(user => {
                this.authUserChangeListeners.forEach(listener => listener(user))
            })
    }

    doSignOut() {
         Auth.signOut()
            .then(() => {
                this.authUserChangeListeners.forEach(listener => listener(null))
            });
    }
}

const withAmplify = Component => props => (
    <AmplifyContext.Consumer>
        {amplifyClient => <Component {...props} amplifyClient={amplifyClient} />}
    </AmplifyContext.Consumer>
);

会话:

const provideAuthentication = Component => {
    class WithAuthentication extends React.Component {
           constructor(props) {
            super(props);

            this.state = {
                authUser: null,
            };
        }

        componentDidMount() {
            this.props.amplifyClient.authUserChangeHandler((user) => {
                this.setState({authUser: user});
            });
        }

        render() {
            return (
                <AuthUserContext.Provider value={this.state.authUser}>
                    <Component {...this.props} />
                </AuthUserContext.Provider>
            );
        }
    }

    return withAmplify(WithAuthentication);
};

const withAuthentication = Component => {
    class WithAuthentication extends React.Component {
        render() {
            return (
                <AuthUserContext.Consumer>
                    {user =>
                        !!user ? <Component {...this.props} /> : <h2>You must log in</h2>
                    }
                </AuthUserContext.Consumer>
            );
        }
    }

    return withAmplify(WithAuthentication);
};

最高一次提供auth上下文:

export default provideAuthentication(App);

然后需要身份验证的页面可以使用它:

export default withAuthentication(MyPage);

我想发生的是,在用户登录之后,我可以设置AuthUserContext,依次更新所有侦听器。但是由于重定向导致整个应用刷新Auth.federatedSignIn()中的承诺,因此无法解决。即使他们确实这样做,这也会导致向用户显示You must log in

有没有办法在仍然使用托管UI的情况下阻止此重定向?也许在另一个选项卡或没有关闭我的应用程序的弹出窗口中启动它?还是我走错路了?只是感觉不太“反应”而导致整页刷新。

任何帮助将不胜感激。我可以根据要求提供更多详细信息。

1 个答案:

答案 0 :(得分:0)

您可以使用Amplify的内置消息传递系统来侦听事件,而不必链接到Auth的承诺。这是我在自定义钩子中的操作方式以及如何处理Redux中呈现的内容。

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

function useAuth({ setUser, clearUser, fetchQuestions, stopLoading }) {
  useEffect(() => {
    Hub.listen('auth', ({ payload: { event, data } }) => {
      if (event === 'signIn') {
        setUser(data);
        fetchQuestions();
        stopLoading();
      }
      if (event === 'signOut') {
        clearUser();
        stopLoading();
      }
    });

    checkUser({ fetchQuestions, setUser, stopLoading });
  }, [clearUser, fetchQuestions, setUser, stopLoading]);
}

async function checkUser({ fetchQuestions, setUser, stopLoading }) {
  try {
    const user = await Auth.currentAuthenticatedUser();
    setUser(user);
    fetchQuestions();
  } catch (error) {
    console.log(error);
  } finally {
    stopLoading();
  }
}