经过身份验证的React组件和路由的更好模式

时间:2016-12-19 01:31:24

标签: reactjs redux react-router auth0 redux-saga

我正在努力为我的React应用添加Auth0身份验证,即使我有它工作,我觉得有更好的方法来解决这个问题。我正在努力为验证逻辑找出更好的模式。

应用设置为react + redux + react-router-redux + redux-saga + immutable + auth0-lock

从顶部开始,App组件定义基本页面布局,BuilderEditor组件都要求用户登录,authenticated()包装每个在负责处理身份验证的高阶组件中。

// index.js

import App from './containers/App';
import Builder from './containers/Builder';
import Editor from './containers/Editor';
import Home from './containers/Home';
import Login from './containers/Login';

import AuthContainer from './containers/Auth0/AuthContainer';

...

ReactDOM.render(
  <Provider store={reduxStore}>
    <Router history={syncedHistory}>
      <Route path={'/'} component={App}>
        <IndexRoute component={Home} />
        <Route path={'login'} component={Login} />
        <Route component={AuthContainer}>
          <Route path={'builder'} component={Builder} />
          <Route path={'editor'} component={Editor} />
        </Route>
      </Route>
      <Redirect from={'*'} to={'/'} />
    </Router>
  </Provider>,
  document.getElementById('app')
);

目前,AuthContainer除了检查isLoggedIn的redux商店外没什么作用。如果isLoggedIn为false,则不允许用户查看该组件,并将其重定向到/login

// containers/Auth0/AuthContainer.js

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { redirectToLogin } from './Auth0Actions';

class AuthContainer extends React.Component {
  componentWillMount() {
    if (!this.props.isLoggedIn) {
      this.props.actions.redirectToLogin();
    }
  }
  render() {
    if (!this.props.isLoggedIn) {
      return null;
    }
    return this.props.children;
  }
}

// mapStateToProps(), mapDispatchToProps()

export default connect(mapStateToProps, mapDispatchToProps)(AuthContainer);

下一篇是Auth0。 Auth0 Lock在“重定向”模式下工作,这意味着用户将离开应用程序登录,然后在/login重定向回应用程序。作为重定向的一部分,Auth0将一个令牌作为URL的一部分附加,需要在应用程序加载时对其进行解析。

const lock = new Auth0Lock(__AUTH0_CLIENT_ID__, __AUTH0_DOMAIN__, {
  auth: {
    redirect: true,
    redirectUrl: `${window.location.origin}/login`,
    responseType: 'token'
  }
});

由于Auth0将重定向到/login,因此Login组件也需要身份验证逻辑。与AuthContainer类似,它会检查redux商店isLoggedIn。如果isLoggedIn为真,则会重定向到根/。如果isLoggedIn为false,则会尝试进行身份验证。

// containers/Login/index.js

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { authenticate, redirectToRoot } from '../Auth0/Auth0Actions';

class Login extends React.Component {
  componentDidMount() {
    if (!this.props.isLoggedIn) {
      this.props.actions.authenticate();
    }
    else {
      this.props.actions.redirectToRoot();
    }
  }
  render() {
    return (
      <div>Login Page</div>
    );
  }
}

// mapStateToProps(), mapDispatchToProps()

export default connect(mapStateToProps, mapDispatchToProps)(Login);

有了这些部分,我与Auth0的集成似乎正在发挥作用。但是,我现在有AuthContainerLogin组件,它们非常相似。我无法将Login组件作为子项放置到AuthContainer,因为登录页面实际上并不要求用户登录。

理想情况下,所有身份验证逻辑都存在于一个地方,但我正在努力寻找另一种方法来实现它,特别是在Auth0重定向的特殊情况下。我不禁想到必须有一个不同的方法,一个更好的模式用于react + redux应用程序中的身份验证流程。

有用的一件事是在应用程序开始初始化之前更好地了解如何在页面加载时调度异步操作。由于Auth0使用回调,我被迫延迟设置redux初始状态,直到Auth0调用注册的回调。在页面加载时处理异步操作的推荐方法是什么?

为了简洁,我省略了一些作品,比如动作和传奇,但如果有帮助,我会非常乐意提供这些作品。

3 个答案:

答案 0 :(得分:1)

我在我的项目中做了同样的事情并且使用reduxreact-router正常工作,只需查看下面的代码:

<强>路由

export default (
  <div>
  <Route path="/" component={AuthenticatedComponent}>
    <Route path="user" component={User} />
    <Route path="user/:id" component={UserDetail} />
  </Route>    
  <Route path="/" component={notAuthenticatedComponent}>
    <Route path="register" component={RegisterView} />
    <Route path="login" component={LoginView} />
  </Route>    
  </div>
);

<强> AuthenticatedComponent

    export class AuthenticatedComponent extends React.Component {
      constructor( props ) {
        super( props );   
      }

      componentWillMount() {
        this.props.checkAuth().then( data => {
          if ( data ) {
            this.props.loginUserSuccess( data );
          } else {
            browserHistory.push( '/login' );
          }
        } );
      }

      render() {
        return (
        <div>
          { this.props.isAuthenticated && <div> { this.props.children } </div> }
        </div>
        );
      }
    }

<强> notAuthenticatedComponent

export class notAuthenticatedComponent extends React.Component {
  constructor(props){
    super(props);    
  }

  componentWillMount(){
    this.props.checkAuth().then((data) => {
      if(data && (this.props.location.pathname == 'login')){
        browserHistory.push('/home');
      }
    });
  }

  render(){
    return (
      <div>
      { this.props.children }
      </div>
    );
  }
}

答案 1 :(得分:1)

可能不是一个完整的答案,对不起。这里要解决的问题很少:

  

理想情况下,所有身份验证逻辑都存在于一个地方

我不太确定这是理想的,取决于你所说的“一个地方”。有两个相似的功能但在某些方面有足够的差异可以保证一点点重复,这没有错。从我可以看到你的代码的逻辑确实略有不同,因此两个组件看起来非常好。

使用路由的componentDidMount道具

而不是onEnter

在组件安装后放置auth逻辑可能会导致在auth逻辑运行之前显示经过身份验证的html的闪烁。从概念上讲,您希望在auth逻辑运行之前完全阻止渲染此组件。 Route onEnter非常适合这种情况。 https://github.com/ReactTraining/react-router/blob/master/docs/API.md#onenternextstate-replace-callback

let authenticate = (nextState, replace) => {
   // check store details here, if not logged in, redirect
}

<Route path={'builder'} onEnter={authenticate} component={Builder} />
  

如何在应用开始初始化之前在页面加载上调度异步操作

这是React Apps / SPA的常见问题。我认为最好的用户体验是立​​即显示内容,可能是加载微调器或“收集用户详细信息”或其他内容。您可以在顶级应用容器中执行此操作,甚至可以在第一次调用ReactDOM.render

之前执行此操作
ReactDOM.render(<SplashLoader />, element)

authCall().then(data =>
  ReactDOM.render(<App data={data} />, element)
).catch(err =>
  ReactDOM.render(<Login />, element)
}

答案 2 :(得分:1)

如果您遵循Thanh Nguyen的回答,请使用React的“构造函数”而不是“ componentWillMount”。按照docs的推荐方法。