Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally securing routes (user registration flow)

I need to implement registration flow using react-router-4. Flow consist of following steps:

  1. Create account (email/pass)
  2. Confirm email
  3. Create profile (profile details like City, Age etc, required fields)

Application requirements:

  • if user not logged in, he always should be redirected to Login page
  • if user logged in, but email not confirmed, he should always be redirect to ConfirmEmail page (regardless of what URL he type in url address bar, even after loading app after few days)
  • if user confirmed email, but didn't yet created profile, I want him to always be redirected to CreateProfile page

So if user didn't finished some step I want always redirect him to this step (even after reloading application and regardless of what URL he type in browser).

As an option I want to restrict user from accessing ConfirmEmail page if he already confirmed it (or restrict access to CreateProfile page if he already created profile).

How to elegantly implement this logic using React router 4? I think it's a core/fundamental feature of application, so I'm looking for good, scalable solution.

Also i'm using Redux, so please note that at some point I already have following variables in Redux state: isLoggedIn, isEmailConfirmed, isProfileCreated.

Thanks.

like image 385
WelcomeTo Avatar asked Dec 14 '17 15:12

WelcomeTo


1 Answers

You didn't specify how or when isLoggedIn, isEmailConfirmed, isProfileCreated will be set so I assumed they will be already somehow set in the redux store before rendering starts.

I think that best tool for this task will be to use inline Route rendering similar to auth workflow example in RR4 docs.

I have made sample CRA app that does what you require.

If you change values of properties in redux/index.js:

const INITIAL_STATE = {
  isLoggedIn: false,
  isEmailConfirmed: false,
  isProfileCreated: false,
}

... the app will use it to render appropriate view, no matter what URL you try to access. For example, if you set isEmailConfirmed=true and isProfileCreated=false then the only route you will have access is /create-profile (CreateProfile component). If the user however completed every step of registration steps and isProfileCreated=true he will then have access to every route except those of registration.

Enhanced Route named AuthorizedRoute:

import React from 'react'
import { Redirect, Route } from 'react-router-dom'
import { connect } from 'react-redux'

const AuthorizedRoute = ({ component: Component, isProfileCreated, isEmailConfirmed, isLoggedIn, ...rest }) => (
  <Route {...rest} render={props => {
    //-- if user is fully registered - grant him every route except registration steps.
    if (isProfileCreated) {
      if (['/', '/create-account', '/create-profile', '/confirm-email'].includes(props.location.pathname)) {
        return <Redirect to="/dashboard" />
      } else {
        return <Component {...props} />
      }
    }

    //-- user is not fully registered so he needs to be redirected to next step...
    if (isEmailConfirmed) {
      if (props.location.pathname === '/create-profile') {
        return <Component {...props} />
      } else {
        return <Redirect to="/create-profile" />
      }
    }
    if (isLoggedIn) {
      if (props.location.pathname === '/confirm-email') {
        return <Component {...props} />
      } else {
        return <Redirect to="/confirm-email" />
      }
    }

    //-- ... or allowed to use `Login` or `CreateAccount` page
    if (props.location.pathname === '/' || props.location.pathname === '/create-account') {
      return <Component {...props} />
    }
    return <Redirect to="/" />
  }} />
)

export default connect(
  (state) => ({
    isProfileCreated: state.isProfileCreated,
    isEmailConfirmed: state.isEmailConfirmed,
    isLoggedIn: state.isLoggedIn,
  }),
)(AuthorizedRoute)

And here are defined components that are aware of this logic:

class App extends Component {
  render () {
    return (
      <div>
        <nav>
          <Link to="/">login</Link>
          <Link to="/create-account">create account</Link>
          <Link to="/confirm-email">confirm email</Link>
          <Link to="/create-profile">create profile</Link>
          <Link to="/dashboard">dashboard</Link>
        </nav>
        <Switch>
          <AuthorizedRoute exact path="/" component={Login} />
          <AuthorizedRoute path="/create-account" component={CreateAccount} />
          <AuthorizedRoute path="/confirm-email" component={ConfirmEmail} />
          <AuthorizedRoute path="/create-profile" component={CreateProfile} />
          <AuthorizedRoute path="/dashboard" component={Dashboard} />
          <Redirect to="/" />
        </Switch>
      </div>
    )
  }
}

I think the hardest part is to actually correctly identify the user and what he is allowed or not to do. Other than that it's just a matter of identifying route he is trying to access and rendering that route or redirecting.

like image 81
Tomasz Mularczyk Avatar answered Oct 22 '22 19:10

Tomasz Mularczyk