Our thoughts, knowledge, insights and opinions

JWT Authentication in a React-Redux app

Are you currently working on JWT authentication in React and Redux App? Don’t you know how to handle it? In this article we will cover a sign in process step by step.

These days, authentication is very important and commonly used aspect in modern web. It is for identifying users and to provide access rights for specific content. When logging, users either enter their usernames and passwords or use 3rd party providers for authentication purposes. In this entry I’ll present how to handle that process from the front-end side with an example using JSON Web Token.

JSON Web Token (JWT) is an open standard that defines a compact and self-contained way to transmit information securely between parties using a JSON object. More about JWT

Tools that we’ll use: react, redux, redux-form, react-router, redux-thunk, axios.

Prelude

After receiving user login data (e.g.: email and password), the server checks if data is valid. If it is, server returns a specific json response containing the token to allow user identification.

JSON Web Tokens consist of three parts, which are:
  • Header - The header usually consists of: the type of the token (JWT) and the hashing algorithm such as HMAC SHA256 or RSA.
  • Payload - contains the claims like RESERVED, PUBLIC and PRIVATE. RESERVED - are predefined claims which are not mandatory but recommended, like iss (issuer), exp (expiration time), sub (subject), aud (audience). PUBLIC - can be defined at will by those using JWTs. PRIVATE - are the custom claims, dedicated for sharing information. The payload is then Base64Url encoded to form the second part of the JSON Web Token.
  • Signature - consists of the encoded header, the encoded payload, a secret and the algorithm specified in the header.

When the user wants to access a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema. Header content should looks like the following: “Authorization: Bearer <token>” The server’s protected routes will check for a valid JWT in the Authorization header, and if it’s valid, the user will be allowed to access protected resources. For security reasons, after logout, the server should pass the token to the blacklist to revoke them.

If the server receives the request without the token, it should send the response of 401 code - unauthorized. In other cases, e.g. failed validation, the server returns 400 code - bad request.

Thanks to HTTP codes and messages, client side application can recognize failed and successful requests and serve responses like tokens or errors.

To begin authorization process, the application has to send request to specific server URL that provides authentication. For example: http://www.sample-website.com/signin

In our case the request must contains two parameters in its body: email and password.

After a successful sign-in, the server returns a specific user token which should be stored in browser memory, for example in the localStorage or sessionStorage, in order to authenticate the user session.

Alert: This post won’t cover the basics of react and redux. If you want learn more about state management in react/redux apps I recommend you to read: Managing React state with Redux.

Configuration:

To start with our application, first let’s begin by initializing React, Redux, and React Router.

Root index.js looks like this:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { BrowserRouter as Router, Route } from 'react-router-dom';

import Navbar from './components/universal/navbar';
import Homepage from './components/homepage';
import Signin from './components/auth/signin';
import Signup from './components/auth/signup';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware()(createStore);
const store = createStoreWithMiddleware(reducers);

ReactDOM.render(
  <Provider store={store}>
    <Router>
      <div>
        <Navbar />
        <Route exact path="/" component={Homepage} />
        <Route path="/signin" component={Signin} />
        <Route path="/secret" component={SecretPage} />
      </div>
    </Router>
  </Provider>,
  document.querySelector('#root')
);

The code shows a typical initialization of react for our web application: redux is used to help managing state, react-redux delivers the provider to establish a connection between the state and our application, and, lastly, react-router-dom, to help defining dynamic routing. Provider requires passing store to be able to use connect() calls. We define createStoreWithMiddleware to be able to use middleware in the future and to pass main reducer file which will allow to propagation of state through the whole application scope. There also was added react-router-dom, which allows to define dynamic routing inside web application. Components that were added to router as routes are: Homepage, Signin and the SecretPage (which ultimately is going to be available only for signed in users).

Sign In component:

It’s time to prepare component of Sign In form. For this purpose we’ll use the handy redux-form package, which allows to manage form state in Redux. Below you can find a very basic implementation of redux-form. The submit function takes values passed into form (email and password) and displays them in the console on click event (for testing purposes).

import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';

class Signin extends Component {
  submit = (values) => {
    console.log(values);
  }

  render() {
    const { handleSubmit } = this.props;
    return (
      <div className="form">
        <div className="container">
          <h2>Sign In</h2>
          <form onSubmit={ handleSubmit(this.submit) }>
            <Field name="email"
                  component="input"
                  type="text"
                  placeholder="Email" 
            />
            <Field name="password" 
                  component="input"
                  type="password" 
                  placeholder="Password" 
            />
            <button type="submit" className="blue">Sign In</button>
          </form>
        </div>
      </div>
    );
  }
}

export default reduxForm({
  form: 'signin'
})(Signin);

To make the form component communicate with the store, it has to be wrapped with reduxForm(). ReduxForm will provide the props for the state and the function to handle the submission process. Also, provided by ReduxForm, the component, helps connecting each input to the store. Redux store has to know how to handle actions coming from the form components. So make it possible we need to pass the formReducer to the store.

import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';

const rootReducer = combineReducers({
form: formReducer
});

export default rootReducer;

Authentication:

It’s the high time to start working on authentication. So now, we are going to:
  • make a connection with the server,
  • pass email and password into the request body,
  • receive token and save inside browser’s memory - localStorage,
  • redirect user to specific route - Secret,
  • handle errors.

To make all of those things possible, we are going to use Action Creators.

Actions are payloads of information that send data from the application to store. Action Creators are exactly what the name suggests, they are functions that create actions. They always return an object with type and payload – type being the action name, and payload the data they carry. In fact we are going to make a few additional steps, listed above. That’s why we need to use a middleware like redux-thunk, which allows to write asynchronous code.

“The Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.”

Installation of redux-thunk is very simple. First we need to install its package, import it in our root index.js and add its instance to applyMiddleware.

import reduxThunk from 'redux-thunk';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);

After redux-thunk is added, we can go forward and create our Action Creator:

import axios from 'axios';

export const AUTHENTICATED = 'authenticated_user';
export const UNAUTHENTICATED = 'unauthenticated_user';
export const AUTHENTICATION_ERROR = 'authentication_error';

const URL = 'http://www.sample-website.com';

export function signInAction({ email, password }, history) {
  return async (dispatch) => {
    try {
      const res = await axios.post(`${URL}/signin`, { email, password });

      dispatch({ type: AUTHENTICATED });
      localStorage.setItem('user', res.data.token);
      history.push('/secret');
    } catch(error) {
      dispatch({
        type: AUTHENTICATION_ERROR,
        payload: 'Invalid email or password'
      });
    }
  };
}

We have added to our application the HTTP client Axios, it allows for an easy way to send asynchronous HTTP requests to REST endpoints. Axios supports the Promise API, allows to cancel or intercept request, response and automatically transforms data to JSON. Thanks to this, our communication with the server will be much faster and easier. We also set some types that we will need to dispatch to the reducer.

Next we create an Action Creator called signInAction where we pass in: email, password and history from the Signin component. Email and password are required in the request body for authentication purposes and the history object will help us to redirect user if the sign in process passes successful. Then, we define async/await function with the dispatch method. In the try block, we define const res with axios method POST with server URL and values from Signin component (email/password). We also added await operator before post method to execute other functions only if the request was done successfully, but, if not, the error is handled in the catch() block. Dispatches an actions of type AUTHENTICATED saves token in localStorage. Then, history.push() redirects the user to the specific route URL.

If user is authenticated, state should returns true or false. In case of an error, state also should contain error message. So, we need to create auth_reducer.js file that handles different cases based on dispatched action types.

import { AUTHENTICATED, UNAUTHENTICATED, AUTHENTICATION_ERROR } from '../actions';

export default function(state={}, action) {
  switch(action.type) {
    case AUTHENTICATED:
      return { ...state, authenticated: true };
    case UNAUTHENTICATED:
      return { ...state, authenticated: false };
    case AUTHENTICATION_ERROR:
      return { ...state, error: action.payload };
  }
  return state;
}

Depending on specific case AUTHENTICATED, UNAUTHENTICATED or AUTHENTICATION_ERROR, the reducer returns a new state with the appropriate boolean value or error message.

Now we have to pass our new auth reducer to the main reducer file as follows:

import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import authReducer from './auth_reducer';

const rootReducer = combineReducers({
  form: formReducer,
  auth: authReducer
});

export default rootReducer;

On this step we have to back to Sign In component and make some changes.

import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { signInAction } from '../../actions';
import { connect } from 'react-redux';

class Signin extends Component {
  submit = (values) => {
    this.props.signInAction(values, this.props.history);
  }

  errorMessage() {
    if (this.props.errorMessage) {
      return (
        <div className="info-red">
          {this.props.errorMessage}
        </div>
      );
    }
  }

  render() {
    const { handleSubmit } = this.props;
    return (
      <div className="form">
        <div className="container">
          <h2>Sign In</h2>
          <form onSubmit={ handleSubmit(this.submit) }>
            <Field name="email"
                   component="input"
                   type="text"
                   placeholder="Email" 
            />
            <Field name="password" 
                   component="input"
                   type="password"
                   placeholder="Password" 
            />
            <button type="submit" className="blue">Sign In</button>
          </form>
          {this.errorMessage()}
        </div>
      </div>
    );
  }
}

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


const reduxFormSignin = reduxForm({
  form: 'signin'
})(Signin);

export default connect(mapStateToProps, {signInAction})(reduxFormSignin);

We have to import signInAction action that we prepared earlier and also connect() method from react-redux. This method will connect our reduxFormSignin and Signin component with actions creators. And now our Actions Creators (signInAction()) are available as a props in our component.

We also have to define mapStateToProps function which takes application state and pass it into component as a props. In our case, we need state.auth.error and use it as this.props.errorMessage, which returns an error in case of authentication failure. We pass our action creator - signInAction to the submit function with the values from the form and with props.history (to be able to redirect user in action). We also defined errorMessageDisplay function that displays errors.

Sign In form should also have a client-side validation. Very fine example you can find in redux-form documentation

Now we can use our authenticated state value, for example to render proper menu items. If user is logged we render Signout and Secret page links, in other case we want Sign In and Sign Up. The code below shows how to achieve that.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';

class Navbar extends Component {
  navbarLinks() {
    if (this.props.authenticated) {
      return [
        <li key="secret"><Link to="/secret">Secret</Link></li>,
        <li key="signout"><Link to="/signout">Sign out</Link></li>
      ];
    }
    return [
      <li key="signin"><Link to="/signin">Sign in</Link></li>,
      <li key="signup"><Link to="/signup">Sign up</Link></li>
    ];
  }

  render() {
    return (
      <nav className="navbar">
        <div className="container">
          <Link to="/"><span className="brand">Auth-app</span></Link>
          <ul>
            {this.navbarLinks()}
          </ul>
        </div>
      </nav>
    );
  }
}

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

export default connect(mapStateToProps)(Navbar);

Auto user authentication:

If the token is saved in localStorage, our application should automatically change the auth state to authenticated before rendering. But currently there’s no code for that. That’s why when we refresh the page, state of user auth comes back to false. To fix this, we have to add dispatch method with type: AUTHENTICATED if there is a proper token in user browser. In root index.js file, we need to make some fixes as follows:

import { AUTHENTICATED } from './actions';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);

const user = localStorage.getItem('user');

if(user) {
  store.dispatch({ type: AUTHENTICATED });
}

ReactDOM.render(
  <Provider store={store}>
    <Router>
      <div>
        <Navbar />
        <Route exact path="/" component={Homepage} />
        <Route path="/signin" component={noRequireAuth(Signin)} />
        <Route path="/signup" component={noRequireAuth(Signup)} />
        <Route path="/signout" component={Signout} />
        <Route path="/secret" component={requireAuth(SecretPage)} />
      </div>
    </Router>
  </Provider>,
  document.querySelector('#root')
);

We have to check if there is an ‘user’ token in localStorage. In case there is, we need to dispatch an action of type AUTHENTICATED into reducers. Thanks to this, new state will return value of authenticated: true.

Sign out:

To add Sign out logic, we just have to create a SignOut component which would trigger specific action. Sample action creator for signout may looks like this:

export function signOutAction() {
  localStorage.clear();
  return {
    type: UNAUTHENTICATED
  };
}

Protected routes:

Currently the user, after successful sign in, is redirected into ‘/secret’ route. In fact this route isn’t protected in anyway. Even not authorized person can go there and see the content. This is behaviour that we want to avoid, because of security reasons. That’s why we want to secure our route with Higher-Order Components (HOC). HOC is able to wrap specific component and extend its functionality. In our case, have to check if the user is authenticated. If the user is not, higher order component redirects to another route for example into Signin. To make it happen we have to create new component, called for example require_auth.js and add specific the logic like:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

export default function (ComposedComponent) {
  class Authentication extends Component {
    componentWillMount() {
      if (!this.props.authenticated) {
        this.props.history.push('/signin');
      }
    }

    componentWillUpdate(nextProps) {
      if (!nextProps.authenticated) {
        this.props.history.push('/signin');
      }
    }

    PropTypes = {
      router: PropTypes.object,
    }

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

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

  return connect(mapStateToProps)(Authentication);
}

When the component is mounting or updating (because of changes to props or state) there is an if statement that check if the user is authenticated. If the user is not, HOC will make a redirection into ‘/signin’ URL. In any other case, nothing happens and the user can go into current component.

Similarly, we can also ‘protect’ routes which should not be available for authenticated user, like Sign In, Sign Up etc. The HOC for it has a very similar logic

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

export default function (ComposedComponent) {
  class NotAuthentication extends Component {
    componentWillMount() {
      if (this.props.authenticated) {
        this.props.history.push('/secret');
      }
    }

    componentWillUpdate(nextProps) {
      if (nextProps.authenticated) {
        this.props.history.push('/secret');
      }
    }

    PropTypes = {
      router: PropTypes.object,
    }

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

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

  return connect(mapStateToProps)(NotAuthentication);
}

An authenticated user while visiting urls like ‘/signin’ or ‘/signup’ is automatically redirected to specific URL, for example ‘/secret’.

To secure specific routes, we just have to wrap those with our Higher Order Components as below:

import requireAuth from './components/hoc/require_auth';
import noRequireAuth from './components/hoc/no_require_auth';
<Route exact path="/" component={Homepage} />
<Route path="/signin" component={noRequireAuth(Signin)} />
<Route path="/signup" component={noRequireAuth(Signup)} />
<Route path="/signout" component={requireAuth(Signout)} />
<Route path="/secret" component={requireAuth(SecretPage)} />

And that’s it. We have made a react-redux app that uses JWT Authentication process.

I attach some links below to repositories which helped me to implement this authorization and to understand a whole process. I hope that this article will help you understand the unclear parts of the authentication process from a front-end point of view.

Other useful links:

You like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.