diego_c
diego_c

Reputation: 819

Server side React Router user authentication

I'm running ReactRouter on top of a Koa server.

My Koa server is configured so that all requests point to 'index.html' (using koa-connect-history-api-fallback), which in turn forwards the requests to the ReactRouter. This all works great, except I'm having trouble figuring out how to do user authentication.

I want to protect my Routes so that users must be logged in to access any of the Routes. The problem is that my login page is one of the Routes, which means a user has to be logged in to access the login page!!

Is there a good way to get around this? For example, in my Koa server could I somehow protect all routes except for the '/login' route? I've read this example, which takes care of the authentication within the ReactRouter, but it seems sketchy to me to have your authentication on the client side. I could be off base though.

In case your curious, I'm working off react-redux-starter-kit

Upvotes: 1

Views: 2410

Answers (2)

nanobar
nanobar

Reputation: 66355

You could authenticate your top level components using a decorator, e.g.

// auth.js
import React, {Component} from 'react';
import { connect } from 'react-redux';

export default function(ComposedComponent) {
    class Auth extends Component {
        static contextTypes = {
            router: React.PropTypes.object
        }

        componentWillMount() {
            if (!this.props.authenticated) {
                this.context.router.push('/');
            }
        }

        componentWillUpdate(nextProps) {
            if (!nextProps.authenticated) {
                this.context.router.push('/');
            }
        }

        render() {
            return <ComposedComponent {...this.props} />
        }
    }

    function mapStateToProps(state) {
        return {authenticated: state.auth.authenticated};
    }

    return connect(mapStateToProps)(Auth);
}

and:

@auth
...your component...

Or you could do it in a less "reduxy" way if you had an auth module:

function requireAuth(nextState, replace) {
  if (!auth.loggedIn()) {
    replace({
      pathname: '/login',
      state: { nextPathname: nextState.location.pathname }
    })
  }
}

render((
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="login" component={Login} />
      <Route path="logout" component={Logout} />
      <Route path="about" component={About} />
      <Route path="dashboard" component={Dashboard} onEnter={requireAuth} />
    </Route>
  </Router>
), document.getElementById('example'))

Upvotes: 3

horyd
horyd

Reputation: 1364

In this instance I would leave the responsibility of protecting routes with ReactRouter, and use match on the server to figure out what route you actually want to render/redirect to when any request comes in.

Importantly, when a request comes to the Koa server, you run it through some authentication middleware that is able to tell you if the user is authenticated and their role. You then want this information reflected in the Redux store. You could either generate the store with an initial state something like this:

{
  user: {
    authenticated: true,
    role: 'admin'
  }
}

, or even better you could dispatch an action on the store where a reducer does this for you.

Now, when you create your routes on the server (passing in your store) React Router will know exactly what is okay and what is not. That is, if you have protected Routes with onEnter by checking user.authenticated, then calling match on the server will honour that and return a redirect to something like /login. An example onEnter might look like this:

const ensureLoggedIn = (nextState, replace) => {
    if (!store.getState().user.authenticated)) {
      replace('/login');
    }
};

You can capture that redirect in redirectLocation which is an argument in the callback to match. Read all about match here. Then you can just use the servers res.redirect with the new location.

Of course, this type of route protection is just convenience, you will want to legitimately protect your API endpoints that contain sensitive information. But this method is invaluable because it uses the same logic for routing on the client and server with pretty much no effort.

Upvotes: 1

Related Questions