Authentication with React.js
Tags: react, tutorial, authentication, beginners
Published: 2020-01-16 14:24:27

---
title: Authentication with React.js
published: true
description: In today’s article, I will show you how to create the front end part of user authentication with React.js.
tags: react, tutorial, authentication, beginners
cover_image: https://thepracticaldev.s3.amazonaws.com/i/dtuhjpksglp13pj2xoip.png
---
We will be using [hooks](https://reactjs.org/docs/hooks-reference.html) and [context](https://reactjs.org/docs/context.html). We will only use the basic concept, you don't need to go too far into this subject for this use case.

#What do we need to do?

1. Create a page that will be accessible only after sign in (we need to create 2 pages: the **SignIn** page where the user logs in and the **Panel** page where the user goes after SignIn. The user can access the Panel page only after SignIn. If he is trying to access Panel directly, we need to redirect him to SignIn);
2. If the user is already logged in and refreshes the page, he should stay on the Panel page and not be redirected to the SignIn page;

#How will we do it?
1. We will create a component called **PrivateRoute** which will be accessible only after passing SignIn page;
2. We will save the user token in **localStorage** so when he quits or refreshes a page, he can access the Panel directly.

Now that we understood what we will do, we can start coding.

##Creating our components: Panel and SignIn
First of all, in our *src* folder, we will create a new folder which is called *screens*. Here we will create **Panel.js** and **SignIn.js**. I will use bootstrap to style my components faster. If you want to do the same and you don't know how to install bootstrap, please look [here](https://react-bootstrap.github.io/getting-started/introduction).

In *src/screens/Panel.js*:
```javascript
import React from "react";
import { Button } from "react-bootstrap";

const Panel = () => {
  const onLogOut = () => {
    console.log('LogOut pressed.'); // we will change it later
  }
  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <div style={{ width: 300 }}>
        <h1 className="text-center"> Hello, user </h1>
        <Button
          variant="primary"
          type="button"
          className="w-100 mt-3 border-radius"
          onClick={onLogOut}
        >
          Log out
        </Button>
      </div>
    </div>
  );
};

export default Panel;
```

In *src/screens/SignIn.js*:
```javascript
import React, { useState} from 'react';
import { Form, Button } from 'react-bootstrap';

const SignIn = () => {
  const [email, setEmail] = useState();
  const [password, setPassword] = useState();

  const onFormSubmit = e => {
    e.preventDefault();
    console.log(email);
    console.log(password);
    // we will change it later;
  };
  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <div style={{ width: 300 }}>
        <h1 className="text-center">Sign in</h1>
        <Form onSubmit={onFormSubmit}>
          <Form.Group>
            <Form.Label>Email address</Form.Label>
            <Form.Control
              type="email"
              placeholder="Enter email"
              onChange={e => {
                setEmail(e.target.value);
              }}
            />
          </Form.Group>

          <Form.Group>
            <Form.Label>Password</Form.Label>
            <Form.Control
              type="password"
              placeholder="Password"
              onChange={e => {
                setPassword(e.target.value);
              }}
            />
          </Form.Group>
          <Button
            variant="primary"
            type="submit"
            className="w-100 mt-3"
          >
            Sign in
          </Button>
        </Form>
      </div>
    </div>
  );
};

export default SignIn;
```
Now we need to create **our router**. We will do it in *App.js*. For navigation in our app, we will be using [react-router-dom](https://reacttraining.com/react-router/web/guides/quick-start). We need to install it with yarn or npm:
```
yarn add react-router-dom
```
Now in *src/App.js* we will create routes for our app.

```javascript
import React from 'react';
import { Switch, BrowserRouter, Route } from 'react-router-dom';
import SignIn from './screens/SignIn';
import Panel from './screens/Panel';

function App() {
  return (
    <BrowserRouter>
        <Switch>
          <Route path="/sign-in" component={SignIn} />
          <Route path="/" component={Panel} />
        </Switch>
    </BrowserRouter>
  );
}

export default App;
```

##Saving the user token in the context

Now we need to create a context to be able to access the user token in multiple components. Even if in this example we have only 2 components but in real-life applications, we will have much more and a lot of them will need user's information. 

We will create a folder called *contexts* in the src folder and will create **AuthContext.js**. 

In *src/contexts/AuthContext.js*:
```javascript
import React, { createContext, useState } from 'react';

export const authContext = createContext({});

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({ loading: true, data: null });
// we will use loading later


  const setAuthData = (data) => {
    setAuth({data: data});
  };
 // a function that will help us to add the user data in the auth;

  return (
    <authContext.Provider value={{ auth, setAuthData }}>
      {children}
    </authContext.Provider>
  );
};

export default AuthProvider;
```
To be able to use our context in the whole application we need to wrap our **App** component in **AuthProvider**. To do this we go in *src/index.js*:
 ```javascript
...
import AuthProvider from './contexts/AuthContext';

ReactDOM.render(
  (
    <AuthProvider>
      <App />
    </AuthProvider>
  ),
  document.getElementById('root'),
);

...
```
Now we need to pass the user credentials to the context from the **SignIn** component. Ideally, you would only send the token to the context, but in this example, we will send the user email, as we do not have a backend to provide us one.

In *src/screens/SignIn.js*: 
```javascript
...
import React, { useState, useContext } from 'react';
import { authContext } from '../contexts/AuthContext';

const SignIn = ({history}) => {
  ...
  const { setAuthData } = useContext(authContext);


  const onFormSubmit = e => {
    e.preventDefault();
    setAuthData(email); // typically here we send a request to our API and in response, we receive the user token.
 //As this article is about the front-end part of authentication, we will save in the context the user's email.
   history.replace('/'); //after saving email the user will be sent to Panel;
  };

  ...

};

export default SignIn;

```
Also, when the user clicks Log out button in the Panel, we need to clear our context. We will add the user email instead of "Hello, user". In *src/screens/Panel.js*:
```javascript
import React, {useContext} from "react";
import { Button } from "react-bootstrap";
import { authContext } from "../contexts/AuthContext";


const Panel = () => {
  const { setAuthData, auth } = useContext(authContext);
  const onLogOut = () => {
    setAuthData(null);
  } //clearing the context
  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <div style={{ width: 300 }}>
        <h1 className="text-center"> {`Hello, ${auth.data}`} </h1>
        <Button
          variant="primary"
          type="button"
          className="w-100 mt-3"
          onClick={onLogOut}
        >
          Log out
        </Button>
      </div>
    </div>
  );
};

export default Panel;
```

##Creating a PrivateRoute 
Now we need to make the **Panel** accessible only after signing in. To do this we need to create a new component called **PrivateRoute**. We are creating *src/components/PrivateRote.js*:
```javascript
import React, { useContext } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { authContext } from '../contexts/AuthContext';

const PrivateRoute = ({ component: Component, ...rest }) => {
  const { auth } = useContext(authContext);
  return (
    <Route
      {...rest}
      render={(routeProps) => (
        auth.data ? <Component {...routeProps} /> : <Redirect to="/sign-in" />
      )}
    />

  );
/*  we are spreading routeProps to be able to access this routeProps in the component. */
};

export default PrivateRoute;
```
If a user is not logged in we will redirect him to the **SignIn** component.
Now we need to use our PrivateRoute in *src/App.js*:
```javascript
...
import PrivateRoute from './components/PrivateRoute';
function App() {
  return (
    <BrowserRouter>
        <Switch>
          <Route path="/sign-in" component={SignIn} />
          <PrivateRoute path="/" component={Panel} />
        </Switch>
    </BrowserRouter>
  );
}

export default App;

```
## Managing localStorage
Now everything works, but if we refresh our **Panel** page we will return to **SignIn**. We want the browser to remember the user. For this reason, we will be using *localStorage*. LocalStorage is a place that stores data in the browser. The problem with localStorage is that it slows down the application. We need to use it wisely and put in useEffect function to ensure the code only executes once. We will do all the manipulation in *src/contexts/AuthContext.js*:

```javascript
import React, { createContext, useState, useEffect } from 'react';

export const authContext = createContext({});

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({ loading: true, data: null });

  const setAuthData = (data) => {
    setAuth({data: data});
  };

  useEffect(() => {
    setAuth({ loading: false, data: JSON.parse(window.localStorage.getItem('authData'))});
  }, []);
//2. if object with key 'authData' exists in localStorage, we are putting its value in auth.data and we set loading to false. 
//This function will be executed every time component is mounted (every time the user refresh the page);

  useEffect(() => {
    window.localStorage.setItem('authData', JSON.stringify(auth.data));
  }, [auth.data]);
// 1. when **auth.data** changes we are setting **auth.data** in localStorage with the key 'authData'.

  return (
    <authContext.Provider value={{ auth, setAuthData }}>
      {children}
    </authContext.Provider>
  );
};

export default AuthProvider;
```
Now in *src/components/PrivateRoute.js*:
```javascript
const PrivateRoute = ({ component: Component, ...rest }) => {
  const { auth } = useContext(authContext);
  const { loading } = auth;

  if (loading) {
    return (
      <Route
        {...rest}
        render={() => {
          return <p>Loading...</p>;
        }}
      />
    );
  }
// if loading is set to true (when our function useEffect(() => {}, []) is not executed), we are rendering a loading component;

  return (
    <Route
      {...rest}
      render={routeProps => {
        return auth.data ? (
          <Component {...routeProps} />
        ) : (
          <Redirect to="/sign-in" />
        );
      }}
    />
  );
};

export default PrivateRoute;
```
That's it. Now if the user is logged in and he refreshes a page he stays on a Panel and is not redirected to SignIn. However, if the user logged out, he can access Panel only by passing by SigIn.

*Why did we use the loading object in our context?*
The **setAuth** function which we used in the context is asynchronous it means it takes some time to really update the state. If we didn't have the loading object, for some milliseconds auth.data would be null. For this reason, we are setting loading to false in our context and return the needed route in the **PrivateRoute** component.