DEV Community

Dayvster 🌊
Dayvster 🌊

Posted on • Edited on • Originally published at dayvster.com

Use React Context for Auth

Tired of writing the same auth code over and over again every time you start a new project? Let's build a simple authentication system using React Context. This way we can easily reuse it in all our projects, completely independat of the state management library we use.

Why React Context and not Redux, recoil, zustand...

React Context is a very powerful tool, but it's not the only tool in the shed. There are a lot of other state management libraries out there that can do the same thing. So why use React Context? Well, it's simple really. React Context is built into React, it's a native feature. This means that we don't need to install any third party libraries to use it. This also means that we can use it in any React project, even if we're not using any other state management library.

In fact handling auth is a damn near perfect candidate for react context, because by it's very nature it belongs in a global application state and we don't expect it to change very often.

What we're going to build

We're going to build a very simple authentication system with react context and react hooks that will allow us to:

  • Store the user's information in the context and local storage
  • Check if the user is logged in
  • clear the user's information from the context and local storage
  • retrieve the user's information from the context and local storage

WE WILL NOT build a fully fledged authentication system with login, logout, registration, etc. We will only build the frontend part, meanig the part that will allow our frontend to know if the user is logged in or not and react accordingly, meaning we will not:

  • send the user's credentials to the backend
  • verify the user's credentials
  • store the user's credentials in the backend

I'll assume you're already familiar with React and TypeScript, I'll also assume you've already have a react or next.js app set up. If you don't, use create-react-app, create-next-app or even t3-app to get started.

Step 1: React Hooks

Let's start off by creating a couple of custom hooks that we'll require for out auth system, first create a directory called hooks in your src directory then create the following files in there:

  • useAuth.ts
  • useUser.ts
  • useLocalStorage.ts

useLocalStorage.ts

This hook will allow us to easily store and retrieve data from localStorage. It's a very simple hook, but it's a good idea to keep it in a separate file so we can reuse it in other parts of our application.

import { useState } from "react";

export const useLocalStorage = () => {
  const [value, setValue] = useState<string | null>(null);

  const setItem = (key: string, value: string) => {
    localStorage.setItem(key, value);
    setValue(value);
  };

  const getItem = (key: string) => {
    const value = localStorage.getItem(key);
    setValue(value);
    return value;
  };

  const removeItem = (key: string) => {
    localStorage.removeItem(key);
    setValue(null);
  };

  return { value, setItem, getItem, removeItem };
};

Enter fullscreen mode Exit fullscreen mode

useUser.ts

This hook will store the user in our context and localStorage.

import { useContext } from "react";
import { AuthContext } from "../context/AuthContext";
import { useLocalStorage } from "./useLocalStorage";

// NOTE: optimally move this into a separate file
export interface User {
  id: string;
  name: string;
  email: string;
  authToken?: string;
}

export const useUser = () => {
  const { user, setUser } = useContext(AuthContext);
  const { setItem } = useLocalStorage();

  const addUser = (user: User) => {
    setUser(user);
    setItem("user", JSON.stringify(user));
  };

  const removeUser = () => {
    setUser(null);
    setItem("user", "");
  };

  return { user, addUser, removeUser, setUser };
};


Enter fullscreen mode Exit fullscreen mode

useAuth.ts

This is the hook where it all comes together. This react hook will be responsible for checking if the user is logged in or not, and if they are, it will return the user object. If the user is not logged in, it will return null.
We'll also expose a login and logout function that we can use to log in and log out the user.

import { useEffect } from "react";
import { useUser, User } from "./useUser";
import { useLocalStorage } from "./useLocalStorage";

export const useAuth = () => {
  // we can re export the user methods or object from this hook
  const { user, addUser, removeUser, setUser } = useUser();
  const { getItem } = useLocalStorage();

  useEffect(() => {
    const user = getItem("user");
    if (user) {
      addUser(JSON.parse(user));
    }
  }, [addUser, getItem]);

  const login = (user: User) => {
    addUser(user);
  };

  const logout = () => {
    removeUser();
  };

  return { user, login, logout, setUser };
};

Enter fullscreen mode Exit fullscreen mode

Step 2: React Context

Now that we have our hooks ready, let's create our context. Create a directory called context in your src directory and create a file called AuthContext.tsx in it.

import { createContext } from "react";
import { User } from "../hooks/useUser";

interface AuthContext {
  user: User | null;
  setUser: (user: User | null) => void;
}

export const AuthContext = createContext<AuthContext>({
  user: null,
  setUser: () => {},
});

Enter fullscreen mode Exit fullscreen mode

Step 3: Wrap your app in the context provider

Now that we have our context ready, let's wrap our app in it. Open your App.tsx file and wrap your app in the context provider.

import { AuthContext } from "../context/AuthContext";
import { useAuth } from "../hooks/useAuth";

const App = () => {
  const { user, login, logout, setUser } = useAuth();

  return (
    <AuthContext.Provider value={{ user, setUser }}>
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
      </div>
    </AuthContext.Provider>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a login page

Now that we have our context ready, let's create a login page. Create a directory called pages in your src directory and create a file called login.tsx in it.

import { useAuth } from '../hooks/useAuth';

const Login = () => {
  const { login } = useAuth();

  const handleLogin = () => {
    login({
      id: '1',
      name: 'John Doe',
      email: 'john.doe@email.com',
    });
  };

  return (
    <div>
      <h1>Login</h1>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode

Step 5: Create a logout button

Awesome now we have a login page all there's missing is a way to log out, let's create a logout button. Create a file called LogoutButton.tsx in your src directory.

import { useAuth } from '../hooks/useAuth';

const LogoutButton = () => {
  const { logout } = useAuth();

  return <button onClick={logout}>Logout</button>;
};

export default LogoutButton;
Enter fullscreen mode Exit fullscreen mode

Further steps

Now that we have our helpful hooks and context ready all we need to do is use them in our application, for example we can call our useAuth hook in our App.tsx to check if the user is logged in or not, if not we can redirect the user to the login page. We can also conditionally render the logout button based on whether the user is logged in or not.

All of this is completely independant from any and all state management libraries.

A note on localStorage

I've used localStorage in this example, but you can use any other storage mechanism you want. For example, you can use cookies The only thing that matters is that you store the user object in a way that you can retrieve it later.
Additionally you should also probably encrypt the user object before storing it in localStorage, but that's out of the scope of this article. Also you should probably allow the user select if they wish to persist their login after leaving the page or not.

IMPORTANT This is not a secure way to store user data, it's just a simple example to show how you can use react hooks to create a simple authentication system. You should optimally use a secure authentication system like JWT or OAuth, but that's out of the scope of this article.

Conclusion

In this article we've learned how to create a simple authentication system using react hooks and context. We've also learned how to persist the user object in localStorage so that the user doesn't have to log in every time they visit the page.

Top comments (7)

Collapse
 
kagz profile image
Dru

Sir thank you for this tutorial, its exactly what I want to implement, kindly can you explain to me where the login, logout returned in the *useUser * hook are coming from.

Collapse
 
dayvster profile image
Dayvster 🌊

those are just examples, but essentially you would implement the login and logout functionality within the hook itself.

So that could be storing the user bearer token in local storage, indexDb or even as a cookie.

Upon logout you optimally want to delete that token, unset that token or invalidate that token from your chosen storage.

The authentication and authorization logic should be optimally handled by the backend.

Collapse
 
vitvoj profile image
Vitaly Voyevoda • Edited

I believe this is a typo. Replace with:

    return {user, addUser, removeUser};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
vannakvy profile image
vannakvy

It is hard to understand. In App. user and setUser.
<AuthContext.Provider value={{ user, setUser }}>

Collapse
 
nakul-047 profile image
Nakul

Thanks for this great article, tried implementing, but i can't find the implementation of setUser function.

Collapse
 
yerassyl profile image
Yerassyl Diyas

Big thanks for this tutorial. Short and informational.
In your app component you use setUser but I dont see it being imported etc.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.