Authenticating Express React App - Part 1

React Apps of any nature will have certain sections that are visible to users who have registered in your app and have logged in. Consider an E-Commerce app, where users can do everything except checkout without being logged in. So how to build such a system with as little pain to the user and as much security to your app as possible. Lets build an Express app with React & Flux driving the frontend. We will focus on Token Based Authentication in this article.

Adding Authentication to any App is not something very complex, but here I'll be putting up an approach that has worked out for me with very little learning. We will see how to put this up with React's Flux pattern. The idea is as following -

  • User enters username and password. On success, receives a Token.
  • Token is stored in Local Storage.
  • State of React Parent Component is populated with necessary user details from Token.
  • On hard refresh, the parent component is refreshed, Token is read and again state is populated with necessary re-directions.
  • On logout, flush out the state and delete Token.
  • Part II of this tutorial will contain the server level actions and how the token could be generated, validated etc.
Required Package

Here is how my package.json looks like. The major packages are React, React-Dom, Express, Flux, Jwt-Decode, React-Router

"dependencies": {
    "express": "^4.14.1",
    "flux": "^3.1.2",
    "react": "^15.5.3",
    "react-dom": "^15.5.3",
    "react-router": "^3.0.2",
    "jwt-decode": "^2.2.0",
},
Understanding Flux

As facebook docs say "Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code."
Nothing more to add from my side on this. Here is an overhaul of the flux process in our app.

React Auth

Lets Get started with Login Container -

// LoginContainer.js
export default class Login extends React.Component {

  constructor() {
    this.state = {
      user: '',
      password: '',
      state: '',
    };
  }

  // Here we handle the Login Event
  login(event) {
    event.preventDefault();
    Auth.login(this.state.user, this.state.password)
      .catch((err) => {
        this.setState(function(prevState, props){
          return {status: "Error"}
        });
      });
   }
  }

  render() {
    return (
      <div>
        <h1>Login {this.state.status}</h1>
        <form>
        <div>
          <label htmlFor="username">Username</label>
          <input type="text" value={this.state.user} name="user" placeholder="UserName" required="required"/>
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input type="password" value={this.state.password} name="password" placeholder="Password" required="required" />
        </div>
        <button type="submit" onClick={this.login.bind(this)}>Submit</button>
      </form>
    </div>
    );
  }
}

Next is Our Auth Utility File

// AuthService.js
class AuthService {

  login(email, password) {
    const options = {
      url: 'http://localhost:9000/user/login',
      method: 'POST',
      body: JSON.stringify({
        "email": email,
        "password": password
      })
    };
    return new Promise((resolve, reject) => {
      request(options, (error, response, body) => {
        if(response.statusCode >= 200 &&  response.statusCode <= 304) {
          body = JSON.parse(body);
          if(body.access_granted)  
            resolve(loginUser(body.token));
          else reject("Email/Pass is Invalid");
        }
        else reject("Email/Pass is Invalid");
      })
    });
  }
}

export default new AuthService()
// Login Action
export function loginUser(token, pathname) {
  Dispatcher.handleAction({
    type: "LOGIN_USER",
    data: token,
  });

  if(pathname) {
    browserHistory.push(pathname);
  }
  else {
    localStorage.setItem('token', token);
    browserHistory.push("/");
  }
}

Couple of things to notice above. We are having a parameter pathname, this does not get passed when the user tries to login because everytime User Logs in, we want the user to go to homepage. Every other time, we will redirect user to the page where he came from.

Now lets write the Routes

We are using react-router to handle the routes. Here is how my routes.js file look like -

// Routes.js
const Routes = () => (
  <Router>
    <Route path="/" component={App}>
      {/* Login Route */}
      <Route path="login" component={Login} />
      <Route component={EnsureLoggedInContainer}>
        <Route path="home" component={Home}/>
      </Route>
    </Route>
  </Router>
);
export default Routes;

The job of the EnsureLoggedInContainer is to listen for navigation to a nested route and ensure that the user is logged in. If the user is logged in, the component does nothing and simply renders its children (the requested route). If the user is not logged in, EnsureLoggedInConatainer should record the current URL for the purposes of later redirection, and then direct users to the login page.

// EnsureLoggedInContainer.js
class EnsureLoggedInContainer extends React.Component {
   constructor(props) {
      super(props)
      this.state = this.getCurrentState();
    }
    getCurrentState() {
      return {
        userLoggedIn: UserStore.isLoggedIn()
      };
    }
    componentDidMount() {      
      if (!this.state.userLoggedIn) {
        browserHistory.push("/user/login")
      }
    }
    render() {
      return this.props.children
  }
}
export default EnsureLoggedInContainer;

The UserStore, like any other store, has 2 functions:

  • It holds the data it gets from the actions. In our case, that data will be used by all components that need to display the user information.

  • It inherits from EventEmmiter. It’ll emit a change event every time its data changes so that Components can be rendered again.

// UserStore.js
class UserStoreClass extends EventEmitter {

  constructor() {
    super();
    this.token = null;
    this.dispatchToken = AppDispatcher.register(this.actionDispatcher.bind(this));
    this.user = null;
    this.isLoggedIn = false;
  }
  addChangeListener(callback) {
    this.on('change', callback);
  }
  removeChangeListener(callback) {
    this.removeListener('change', callback);
  }
  getToken() {
    return this.token;
  }
  getUser() {
    return this.user;
  }
  isLoggedIn() {
    return this.isLoggedIn;
  }
  emitChange() {
    this.emit('change');
  }
  
  // Registering the Dispatcher
  actionDispatcher(payload) {
    switch (payload.action.type) {

      case 'LOGIN_USER':
        const token = action.data;
        this.token = token;
        this.user = jwtDecode(token);
        this.isLoggedIn = true;
        this.emitChange();
        break;
    }
  }
}
export default new UserStoreClass();

Now, lets call any API. We will always have access to the user Data in UserStore.
Here is how you can attach the token to the API -

const token = UserStore.getToken();
fetch(url, {
  headers: {
    'token' : token
  },
})
Conclusion

We have implemented Token Based Authentication in React App. In Part II, I will be going through how we can handle the server events, add Redis to the flow, Generate token and persist it for any future validation. In the mean time grab the code from GITHUB.