ReactJS Context with basic Authentication and Routing

ReactJS | useContext | React Router | custom Auth

Brandon Blankenstein
7 min readApr 18, 2022

React Hooks and React Router have simplified the Developer experience when creating apps that allow for Authentication, Routing, and global/local state. This article will walk through the implementation of these concepts at the top level of an app and show how simple this can be!

  • *Authentication is typically checked on the backend, this article only shows how easy it can be to handle authentication on the frontend and allow access to certain pages.
  • *The repository with this code can be found here at brawblan/react-router-context-auth-article.

Project Setup

To get started we’ll need to:
- create our React App
- install React Router
That’s it! Hooks come packed into React and the Authentication we’re using is custom (from your own backend).

index.js Setup with React Router

Luckily, React Router has the setup ready in their docs! I have everything ready to go below, but if you run into issues then checking their docs will be your best option. (linked above in the Project Setup)

// src/index.js fileimport * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);

This will wrap your entire App with React Router and allow you to utilize easy routing in the project.

Basic App.js Setup

React Router also includes setup for the main App.js file.
Now that our app is wrapped in the <BrowserRouter> in the root index.js file we can start utilizing routes.
Below is the React Router example for a simple setup.

// src/app.jsimport * as React from "react";
import { Routes, Route, Link } from "react-router-dom";
import "./App.css";

function App() {
return (
<div className="App">
<h1>Welcome to React Router!</h1>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Routes>
</div>
);
}

We can now wrap our different components (<Route>) in the <Routes> wrapper that holds all of the different places we can go in our app.
Each <Route> component needs a path and an element.
The path is going to be what we see in the url. ie. The way to navigate to and find that page.
The element will be the component that we want to render on that path.

Creating our Components to visit

Finally, we can create our components that we want to visit! Once again, React Router saves us all by providing some simple components that go along with the App.js example that we have above.

// src/app.js
// below our App() component
function Home() {
const [userLogin, setUserLogin] = useState('')

const location = useLocation()
const navigate = useNavigate()
const onChange = ({target: {value}}) => {
setUserLogin(value)
}
const onSubmit = (e) => {
e.preventDefault()
if (!!userLogin) {
const { from } = location.state || { from: { pathname: '/private' }}
authService.loginUser(userLogin)
navigate(from, { replace: true })
}
}
return (
<>
<main>
<h2>Welcome to the homepage!</h2>
<p>You can do this, I believe in you.</p>
</main>
<nav>
<Link to="/about">About</Link>
<br />
<Link to="/private">Private</Link>
<br />
<br />
<form onSubmit={onSubmit}>
<input onChange={onChange} type="text" placeholder='type "true" and login' />
<button type="submit">Login</button>
</form>
</nav>
</>
);
}
function About() {
return (
<>
<main>
<h2>Who are we?</h2>
<p>
That feels like an existential question, don't you think?
</p>
<h3>Tried to login without typing "true"</h3>
<p>
Go back and try again!
</p>
</main>
<nav>
<Link to="/">Home</Link>
</nav>
</>
);
}

Inside each component is the content that we’d like displayed and a <Link> component. This is the important feature that makes everything so easy!
Inside the <Link> we provide the route (to=”?”) and we give the inner HTML for our users.

That’s it for React Router! Next we’ll be setting up our Authentication and jumping into useContext.

Basic Custom Authentication

I’m not going to go into utilizing a backend for authentication, but what I’m showing you will be a good start to setup the frontend interaction with the backend. For this we’ll need to create a file called “services.js”. While we won’t be making API calls here this is the file that you would eventually make those with.

// src/services.jsclass User {
constructor() {
// this would be where you create the User data to utilize later
this.isLoggedIn = false
}

setIsLoggedIn(loggedIn) { this.isLoggedIn = loggedIn }
}
// ^^^ We could use this file above to set anything specific
// to the user that is logging in. Keeping our files and classes
// separated like have been allows for the separation of concerns.
// We can keep our code clutter free by separating different actions and objects.
export class AuthService extends User {
constructor() {
super()
// we won't be using this, but this is where you would extend any other properties you'd like
// this.authToken = ''
// this.bearerHeader = {}
}
// #setAuthToken(token) { this.authToken = token }
// #setBearerHeader(token) {
// this.bearerHeader = {
// 'Content-Type': 'application/json',
// 'Authorization': `bearer ${token}`
// }
// }
// getBearerHeader = () => this.bearerHeader
loginUser(word) {
if (word === 'true') {
this.setIsLoggedIn(true)
}
}
logoutUser() {
this.setIsLoggedIn(false)
}
// async loginUser(email, password) {
// database endpoint logic to login user
// }
}

In this file we will be able to Login and Logout. If we were hitting an API then we’d be able to also set the authToken and bearerHeader for the browser to keep up with our auth.

Creating our AuthProvider

To create the parent that controls our Authentication, and ultimately all of our Context we’ll just create another component that will take our

<Routes>
<Route>....
</Routes>

as its children.
For this we’ll need to import our AuthService, create a new Context, create the <AuthProvider> component, and finally we’ll utilize it in our App.

import React, { useState, useContext, createContext } from "react";
import { Routes, Route, Link, Navigate, useLocation, useNavigate }
from "react-router-dom";
import { AuthService } from './services';
import "./App.css";
const authService = new AuthService()export const UserContext = createContext()const AuthProvider = ({ children }) =>
const context = {
authService,
someFunction: () => {
// we can pass a function here
}
}
return (
<UserContext.Provider value={context}>
{children}
</UserContext.Provider>
)
}
export default function App() {
return (
<div className="App">
<h1>Welcome to React Router!</h1>
<AuthProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Routes>
</AuthProvider>
</div>
);
}

We define a variable “authService” as a new instance of our AuthService. This allows us to use the constructor and all methods we created inside of that service.
Next we define our variable that will be able to carry Context to all components of the app. The export is included since that is how it will be used in most apps (separate folders for components).
Finally, we create our Auth component and use it in our app. Our Auth component has the child components as props and it will pas an object of all of our services as the prop of the <UserContext.Provider> component. The context provider is the component that truly allows us to utilize Context throughout the app. To finish we will wrap all of our components in the <AuthProvider> component so that all the children will now gain access to our services and Context.

Creating our Private Component

To create and use a private component that requires a login we will need to create a <PrivateRoute> component to handle whether or not the user has access and then use that as a Parent component to our private components.

<PrivateRoute>
This component will allow us to pass the child components as props and handle whether or not the user can access them.
We create our context by using useContext and passing our UserContext to give it access to our services and any functions that we might create in the AuthProvider context object.
If we are not logged in then we will pass the <Navigate> component as the child and this will take us back to the “about” page or wherever we wish to send our users that try to access a page they don’t have access to. The <Navigate> component is the new React Router way of redirecting the user, with the old way using the <Redirect> component.
If we are logged in then we will return the children of our <PrivateRoute> component, which will be the components that we are only allowing those with access to view.

function PrivateRoute({ children }) {
const context = useContext(UserContext)
const { isLoggedIn } = context.authService

if (!isLoggedIn) {
return <Navigate to="/about" replace />
}
return children
}

<Private> Component
The component here is just a placeholder for whatever component you’d like to hide behind your authentication.

function Private() {
const location = useLocation()
const navigate = useNavigate()
const onLogout = (e) => {
e.preventDefault()
const { from } = location.state || { from: { pathname: '/' }}
authService.logoutUser()
navigate(from, { replace: true })
}
return (
<>
<main>
<h2>This is the Private page!</h2>
<h2>We are here to love!</h2>
<p>
If nothing else, we have love.
</p>
</main>
<nav>
<Link to="/">Home</Link>
<br />
<Link to="/about">About</Link>
</nav>
<br />
<button onClick={onLogout} type="submit">Logout</button>
</>
);
}

This component can only be accessed if the user is logged in.
— — HOW? you may ask

<PrivateRoute> as a wrapper
We use the <PrivateRoute> component as the parent to the private routes and use the logic from earlier to allow or deny access to these components.

export default function App() {
return (
<div className="App">
<h1>Welcome to React Router!</h1>
<AuthProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route
path="/private"
element={
<PrivateRoute>
<Private />
</PrivateRoute>
}
/>
</Routes>
</AuthProvider>
</div>
);
}

Now anytime we try to access our private components we’ll have to pass through the authentication logic we wrote in our <PrivateRoute> component.

All Done

That’s it! We have now utilized ReactJS, React Hooks, React Router, and our custom Authentication to create a simple app that allows keeping a page private.
The repository with this code can be found here at brawblan/react-router-context-auth-article.

Tip or Quote from Brandon:

Today I have a tip!

Try to surround yourself with good people. It seems easy enough, but I see many, including myself, struggle with it.
For the most part, we can tell whether someone will be a good influence on us or maybe we’ll be the good influence on them. Like everything else, though, there must be a balance.
I’ve heard it once that we should spend approximately 33% of our time with people on our level, ~33% of our time with people above our level, and ~33% with people below our level.
This balance can create great progress in your life and in the lives of those around you. The purpose of each is simple: discuss, learn, give.
Make sure to always live in love and show others light.

--

--