Implementing JWT Authentication in MERN stack with GraphQL: Part II

Ping Cheng
15 min readFeb 17, 2019

--

In the first part of this tutorial, we have implemented the server-side of this application using Express.js, Mongoose and GraphQL and it is capable of performing login, sign up and verify with our defined queries and mutations.

What we need now is to have a user interface that allows us to use those queries and mutations and display the necessary components depending on the user’s authentication status. Let’s first plan on the different routes and components that will be displayed in those routes:

/ : the main entry of the website. Depending on the user’s authentication status, we can display two different components.

/login: login page that allows a user to input email and password.

/signup : sign up page that allows a user to create an account by providing his/her email, password and password confirmation.

Overall these are the only routes we need in this tutorial. Pretty simple, huh?

Now let’s begin by creating our project folder.

Environment Setup

In the previous tutorial, we created our server side from scratch with npm init command. For this tutorial, we will be using create-react-app to set up our project, so that there won’t be any need to deal with build configurations.

If you haven’t yet used create-react-app, run the following command to install it globally:

npm i -g create-react-app

Once installed, go to our mern-graphql-jwt folder and create our React app:

create-react-app client

Do the same as what create-react-app suggests:

cd client
npm start

Open up localhost:3000 and you should be able to see this page:

I know it feels comfortable staring at this icon spinning and spinning, but unfortunately we are going to delete all the contents of our home page 😛

…or let’s actually keep it and use the same style 🌟

// src/App.jsimport React from 'react';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Home</h1>
<p>Homepage for everyone</p>
</header>
</div>
);
}
export default App;

Since we are not using the React logo icon anymore, you may want to delete the file.

We will have two different links to access both the login page and signup page from our homepage, so let’s first install our dependencies for react-router as well as the other dependencies:

npm i react-router react-router-dom redux redux-react-hook redux-logger axios

Now that we have all the packages needed, let’s start modifying our file structures so it becomes more organized:

  1. Create components/ folder under src/

2. Create App/ folder under components/

3. Rename App.js into index.js and move it with App.css into App/ folder

4. Modify src/index.js so it imports App as such:

import App from './components/App';

Since the contents within our App component will be dynamic, let’s extract the contents inside the <header> tag into src/components/Home/index.js :

// src/components/Home/index.jsimport React from 'react';function Home() {
return (
<>
<h1>Home</h1>
<p>Homepage for everyone</p>
</>
)
}
export default Home;

Defining routes

Before importing react-router into our App component, let’s first define our route paths as constants by creating a file named routes.js under src/constants/:

// src/constants/routes.jsexport const HOME = '/';
export const LOGIN = '/login';
export const SIGN_UP = '/signup';

Back to our App component, let’s import the necessary modules from react-router-dom as well as our Home component:

import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom';
import Home from '../Home';
import * as routes from '../../constants/routes';

and define different routes which will render components depending on user’s current location:

// src/components/App/index.js...function App() {
return (
<Router basename={process.env.PUBLIC_URL}>
<div className="App">
<header className="App-header">
<Switch>
<Route exact path={routes.HOME} component={() => <Home />} />
</Switch>
</header>
</div>
</Router>
);
}
export default App;

Then you should be able to see our homepage at localhost:3000.

Let’s also define another route, which is only displayed if a user accidentally accesses a non-defined path. We’ll create an index.js under src/components/NotFound:

//src/components/NotFound/index.jsimport React from 'react';function NotFound() {
return <h1>Oops...Page does not exist!</h1>
}
export default NotFound;

Back to our App component, let’s import our NotFound component:

// src/components/App/index.jsimport NotFound from '../NotFound';

and add another route:

<Route exact path={routes.HOME} component={() => <Home />} />
<Route component={NotFound} />

Now if you try to access somewhere, say at/random, you should be able to see the following content:

Managing Redux store

After setting up the routers, let’s now deal with our Redux store.

Create index.js under src/store/ :

// src/store/index.jsimport { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
const store = createStore(null, applyMiddleware(logger));export default store;

Since we haven’t defined our reducers yet, let’s leave the first argument of createStore as null.

Now we can import our store in src/index.js:

// src/index.jsimport { StoreContext } from 'redux-react-hook';
import store from './store';
...

and wrap our App component with StoreContext.Provider:

// src/index.js...ReactDOM.render(
<StoreContext.Provider value={store}
<App />
</StoreContext.Provider>,
document.getElementById('root'));
...

Now we should be able to retrieve data from our Redux store and dispatch actions anywhere inside our App component.

Defining reducers

Let’s create an index.js and session.js inside src/reducers/, and deal with our sessionReducer first because we will be combining it with other reducers inside index.js:

// src/reducers/session.jsconst INITIAL_STATE = {
authUser: null,
loading: true
};
function sessionReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case "SET_AUTH_USER": {
return { authUser: action.authUser, loading: false };
}
default: return state;
}
}
export default sessionReducer;

When loading our application, we will dispatch an action called “SET_AUTH_USER” that is going to set authUser as an object containing the authenticated user’s information if successful, otherwise it will be set as null. This action will also be used when user performs login and sign up.

loading will be used to show a loading message or icon while accessing the web page. It is set as true by default.

It would be cumbersome to always use pure strings when dispatching our actions, so let’s define them as constants inside src/constants/action_types.js :

// src/constants/actions_types.jsconst SET_AUTH_USER = "SET_AUTH_USER";

Now back in our sessionReducer we may import this action and use it easily:

// src/reducers/session.jsimport { SET_AUTH_USER } from '../constants/action_types';
...

Inside src/reducers/index.js we will import sessionReducer and export it with other reducers if necessary in the future. It would be easier and more organized if we need extra reducers in the future, so that is why we are combining our sessionReducer although there are no other reducers yet.

import { combineReducers } from 'redux';
import sessionReducer from './session';
const rootReducer = combineReducers({
sessionState: sessionReducer,
});
export default rootReducer;

After dealing with our Redux store, we can now split our components depending on user’s authentication status by retrieving its state with Redux.

Splitting up components

As we have previously discussed, our Home component will display two different components depending on user’s authentication status, so let’s actually make it work.

Inside src/components/Home , we will totally have 3 different files:

  • auth.js: component to be displayed if user is authenticated
  • nonauth.js: component to be displayed if user is not authenticated
  • index.js: decides which component to display by checking user’s authentication status

We previously had some contents inside our Home component, however those are contents that we want it to be displayed when a user is not yet authenticated, so let’s put those inside nonauth.js:

// src/components/Home/nonauth.jsimport React from 'react';function NonAuthHome() {
return (
<>
<h1>Home</h1>
<p>Homepage for everyone</p>
</>
)
}
export default NonAuthHome;

and in auth.js we will simply put a normal header for the time being:

// src/components/Home/auth.jsimport React from 'react';function AuthHome() {
return (
<>
<h1>Home</h1>
<p>You are logged in</p>
</>
)
}
export default AuthHome;

In the main Home component, we will import both auth and nonauth components and use redux-react-hook to map our state.

This is how easy it gets, nice and clean.

You may also want to check whether it actually works by changing authUser from null to true inside your sessionReducer and see how the homepage is different than what it were.

Our next step will be creating both a login and sign up component.

Design and modify layouts

Let’s first create two folders and files for these two components:

// src/components/Signup/index.jsimport React from 'react';function Signup() {
return <h1>Sign up</h1>
}
export default Signup;// src/components/Login/index.jsimport React from 'react';function Login() {
return <h1>Login</h1>
}
export default Login;

Now import these two components in our App component and add them into our routes:

// src/components/App/index.js...import Signup from '../Signup';
import Login from '../Login';
...<Route exact path={routes.HOME} component={() => <Home />} />
<Route exact path={routes.SIGN_UP} component={() => <Signup />} />
<Route exact path={routes.LOGIN} component={() => <Login />} />
<Route component={NotFound} />
...

After inserting these routes, you may see the contents of these components if you try to access these by directly changing the URL. However, we would like the user to be able to access these links by clicking from the homepage, so let’s implement a navigation bar on the top of our page and put all the links there:

// src/components/Navigation/index.jsimport React from 'react';
import { Link } from 'react-router-dom';
import * as routes from '../../constants/routes';
function Navigation() {
return (
<div className="navbar">
<div className="navbar-left">
<Link to={routes.HOME}>HOME</Link>
</div>
<div className="navbar-right">
<Link to={routes.SIGN_UP}>SIGN UP</Link>
<Link to={routes.LOGIN}>LOGIN</Link>
</div>
</div>
)
}
export default Navigation;

and import Navigation component in our App component:

// src/components/App/index.js...import Navigation from '../Navigation';function App() {
return (
<Router basename={process.env.PUBLIC_URL}>
<div className="App">
<Navigation />
<header className="App-header">
<Switch>
<Route exact path={routes.HOME} component={() => <Home />} />
<Route exact path={routes.SIGN_UP} component={() => <Signup />} />
<Route exact path={routes.LOGIN} component={() => <Login />} />
<Route component={NotFound} />
</Switch>
</header>
</div>
</Router>
);
}

We can now directly access both login and sign up pages from the navigation bar. Let’s also design their layouts:

// src/components/signup/index.jsimport React from 'react';function Signup() {
return (
<>
<h1>Sign up</h1>
<div className="auth-form">
<form>
<input className="form-input" type="email" placeholder="Email" />
<input className="form-input" type="password" placeholder="Password" />
<input className="form-input" type="password" placeholder="Confirm password" />
<input className="form-submit" type="submit" value="Register" />
</form>
</div>
</>
)
}
export default Signup;// src/components/login/index.jsimport React from 'react';function Login() {
return (
<>
<h1>Login</h1>
<div className="auth-form">
<form>
<input className="form-input" type="email" placeholder="Email" />
<input className="form-input" type="password" placeholder="Password" />
<input className="form-submit" type="submit" value="Login" />
</form>
</div>
</>
)
}
export default Signup;

As we planned, we should get the email, password and password confirmation when a user signs up a new account, thus we are creating three input fields respectively; when a user logs in, we only need an email and password for verification.

So far we have only dealt with the components and the necessary text fields, so it should look pretty ugly by now. Let’s do some styling inApp.css :

Looks pretty satisfying 🍭.

Now we are ready to actually start implementing the functionalities for sign up and login!

Handling sign up form

First thing we have to do is to store our form input values into our component state. Let’s use the latest React Hooks to perform the magic!

  1. Import useState from React
import React, { useState } from 'react;

2. Declare our state variables

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirm, setConfirm] = useState("");

3. Write a handler method for input onChange

const handleChange = setter => e => {
setter(e.target.value);
}

4. Control input values with our state variables and handler

// src/components/Signup/index.js<form>  <input className="form-input" type="email" placeholder="Email" value={email} onChange={handleChange(setEmail)} />  <input className="form-input" type="password" placeholder="Password" value={password} onChange={handleChange(setPassword)} />  <input className="form-input" type="password" placeholder="Confirm password" value={confirm} onChange={handleChange(setConfirm)} />  <input className="form-submit" type="submit" value="Register" /></form>

It is totally optional to additionally create a handler function, but I personally feel that it looks much cleaner and easier to understand when reading the code.

Right now, we need a submit function that will send our user inputs into the server and retrieve back the results. We will be using axios to fetch our data and include our GraphQL queries in the request body.

Remember to run your server as we are going to send requests to it.

Since we will be dispatching SET_AUTH_USER action and use react router to push us to homepage after signing up, let’s import the necessary modules for Redux and axios:

// src/components/Signup/index.jsimport React, { useState } from 'react';
import axios from 'axios';
import { useDispatch } from 'redux-react-hook';
import { withRouter } from 'react-router-dom';
import
* as actions from '../../constants/action_types';

We’ll wrap withRouter to our component so we can access history.push method from its props and also get our dispatch method from useDispatch:

// src/components/Signup/index.js...function Signup(props) {
const disptach = useDispatch();
...}export default withRouter(Signup);

We are now ready to write a function called submit that will be triggered when a user submits the form.

Inside this function, we will make a POST request using axios to our GraphQL server including a query inside the request body. We have already tested our queries in the previous tutorial, and this is how it would look like if we are passing it inside our request body:

const requestBody = {
query: `
mutation {
createUser(userInput: {
email: "${email}"
password: "${password}"
confirm: "${confirm}"
}) {
_id
token
email
}
}
`
};
// retrieve result from server
const { data } = await axios.post('http://localhost:5000/graphql', requestBody);

By default, axios will return a response that has a property data, and inside that data there is also the output coming from the GraphQL server called data, so in order to access the actual results we will have to call data.data.sth 😅

Let’s try to handle our response from the server by first checking whether an error has occurred in an if statement:

if (data.errors) {
console.log(data.errors[0].message);
}

data.errors[0].message is going to be the error message that is being thrown from our server application, and we’ll think of how to handle and display this message on our interface later on.

Suppose that there is no error during the process, we should be able to obtain _id, token and email from data.data.createUser, so we will dispatch SET_AUTH_USER action and set authUser with the user’s id and email as well as storing the token into our localStorage:

if (data.errors) {
console.log(data.errors[0].message);
}
else {
const { _id, token } = await data.data.createUser;
// dispatch action and set new value for authUser
dispatch({
type: actions.SET_AUTH_USER,
authUser: {
_id,
email
}
})
// store token into our localStorage
localStorage.setItem('token', token);
// redirect url to homepage
props.history.push(routes.HOME);
}

In the end this is how our submit function looks like:

Try it out to see if it works. If you are redirected to your homepage, then it means that you are now actually logged in. For real!

Now let’s go back to handle our error message as well as a loading indicator that will be triggered a user submits the form. We will create two state variables called error and loading.

const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);

loading and error will be set throughout the submit function. When submit is triggered, we would like loading to be set true:

const submit = async (e) => {
e.preventDefault();
setLoading(true);
...
}

And if a user encounters an error, we will set the error message to error and set loading to false :

if (data.errors) {
setError(data.errors[0].message);
setLoading(false);
}
else {
setError(null);
setLoading(false);
...
}

And do the same inside catch:

catch (e) {
setError(e);
setLoading(false);
}

As for displaying the error message and loading indicator, we just need to modify the JSX part a little bit by adding a little div before the submit button:

<div><span style={{ color: "red" }}>{error || ""}</span></div><input className="form-submit" type="submit" value={loading ? "Verifying..." : "Register"} />

Test it yourself!

Let’s now deal with our login component. In fact as you can see, these two components will look very similar except for having different queries and input fields. If you’d like, try to implement the login component on your own, it should be a good practice for yourself!

In the end it’s pretty much simply copy pasting except for removing confirm and modifying the query.

They both look hella similar.

Create logout functionality

Now that we can see the homepage for authenticated users, we must also switch our navigation bar accordingly. We’ll split our navigation component the same way we did for our Home component, depending on the user’s authentication status.

First create auth.js and nonauth.js under Navigation/ folder and extract the content of index.js into nonauth.js, which contains a component named NonAuthNavigation .

The content of auth.js will look pretty similar to nonauth.js, except that we are writing an extra logout function that will:

  • dispatch SET_AUTH_USER action by setting authUser to null
  • remove user’s token from localStorage

Now let’s import the necessary modules:

import React from 'react';
import { useDispatch } from 'redux-react-hook';
import { Link } from 'react-router-dom';
import * as routes from '../../constants/routes';
import * as actions from '../../constants/action_types';

and get dispatch on top of AuthNavigation’s body:

// src/components/Navigation/auth.jsfunction AuthNavigation() {
const dispatch = useDispatch();
...
}

On the right side of this navigation bar will be a link for logging out. By clicking this link, the user should be redirected back to our homepage and trigger the logout function:

/// src/components/Navigation/auth.jsfunction AuthNavigation() {
...
return (
<div className="navbar">
<div className="navbar-left">
<Link to={routes.HOME}>HOME</Link>
</div>
<div className="navbar-right" style={{ justifyContent: "flex-end" }}>
<Link to={routes.HOME} onClick={logout}>LOGOUT</Link>
</div>
</div>
)
}

The only thing missing now in this component is the logout function, which is actually pretty simple:

function logout() {
dispatch({ type: actions.SET_AUTH_USER, authUser: null });
localStorage.removeItem('token');
}

Voilà! Now any user can register a new account, log in, and log out smoothly. What else is missing? Automatic login.

Create a custom hook for authentication

As we need to verify a user’s authentication status whenever the user accesses the website, we will need to handle this in the background before the user sees anything on the screen. In the past, we could have written a Higher Order Component that wraps our App component which authenticates the user inside ComponentDidMount, but let’s actually try to implement that using React Hooks!

Create an index.js file under src/components/WithAuthenticate. We will write a hook called useWithAuthenticate that gets the dispatch method on the top of its body and call a function named authenticate inside its useEffect method:

// src/components/Withauthenticate/index.jsimport { useEffect } from 'react';
import { useDispatch } from 'redux-react-hook';
import axios from 'axios';
import * as actions from '../../constants/action_types';
async function authenticate() { ... }function useWithAuthenticate() {
const dispatch = useDispatch();
useEffect(() => {
authenticate(dispatch);
}, [])
}
export default useWithAuthenticate;

The main logic will be inside authenticate function. Here are the steps for verifying user’s token:

  1. Retrieve user’s token from localStorage
  2. Make a POST request to our GraphQL server with a GraphQL query inside the request body
  3. If the user information is included in the response, dispatch SET_AUTH_USER action by setting authUser with this information; otherwise remove token from user’s localStorage and dispatch by setting authUser to null
  4. Dispatch action in the other failed cases as it will indicate that the verification has ended by setting loading to false

You may try to implement the function by following these steps. Here is how the function looks like:

And finally, let’s use this hook in our App component and also make a loading indicator so that our page gets rendered once the verification is done.

Import the necessary modules:

// src/components/App/index.jsimport React, { useCallback } from 'react';
import useWithAuthenticate from '../WithAuthenticate';
import * as routes from '../../constants/routes';
import { useMappedState } from 'redux-react-hook';

And on the top of App component’s body add these few lines:

function App() {
useWithAuthenticate();
const mapState = useCallback((state) => ({
loading: state.sessionState.loading
}), [])
const { loading } = useMappedState(mapState); if (loading) return <h1>Loading...</h1> return ( ... )
}

Nice and clean. Although the loading text will only be shown in a very short amount of time, you should still be able to see it and how we won’t be seeing a sudden change between the authenticated and the unauthenticated homepages.

(It seems that it took quite some time for my computer to load. RIP Internet)

Ending

This is the end of a two-part tutorial. In fact, this project itself can serve as a boilerplate for anyone who’s aiming to start a whole new project with the basic authentication mechanisms. There are potentially many things to be improved in this project, and I would really appreciate if you can give some feedback.

If you have encountered any problem while following this tutorial, I have uploaded the entire project on a Git repository.

Thank you so much for reading. If you liked this article, feel free to share this article with your friends.

--

--

Ping Cheng

Frontend Engineer @LINE, Next.js | React.js | Typescript fanatic