Authentication in React with Supabase

Authentication in React with Supabase

How to implement User Authentication in React using Supabase

User authentication is a crucial aspect of many web applications. It allows users to securely create accounts, log in, and access protected resources. Implementing user authentication can be complex and time-consuming, especially when developing an application from scratch. In this article, we will implement user authentication in React applications with the help of Supabase.

Supabase is an open-source alternative to Firebase that provides many features, such as authentication, database, storage, edge functions, and real-time APIs, which can be used to build real-time applications. Supabase eliminates the need for having a dedicated backend for the React applications. So, React developers can develop a FullStack web app just with React and Supabase.

In this article, you will learn how to implement User Authentication in a React Application using Supabase's Email Auth feature.

React Project Setup with Supabase

If you haven't created an account in Supabase already, head over to its official website and create an account by clicking here

After creating the account, create a project in Supabase by clicking on New Project and entering the required details. You can select the data server closest to your region so that the app will be faster. You can refer to the image below for creating the project.

Supabase Project Setup

Once the project is created, you will get a screen with API keys, shown in the screenshot below.

Project API keys

Copy the anon|public and URL keys, which we will use later in our React Project.

Now, Let's set up a React project. We will use vite to set it up. Run the below command to set up the project in the react-supabase-auth directory.

# npm
npm create vite@latest react-supabase-auth -- --template react

# Yarn
yarn create vite react-supabase-auth -- --template react

Once the above step is done, Run the below commands to start the local dev server.

# npm
npm install
npm run dev

# yarn
yarn
yarn dev

It will open the local dev server.

Optional: You can remove all the unnecessary files (icons, CSS files) generated in the above step.

Next, Let's install the required dependencies by running the below command.

# npm
npm install @supabase/supabase-js bootstrap react-bootstrap react-router-dom

# yarn
yarn add @supabase/supabase-js bootstrap react-bootstrap react-router-dom

The dependencies are explained below.

  • @supabase/supabase-js: Client library that provides necessary API to interact with Supabase.

  • bootstrap: Used for Styling.

  • react-bootstrap: This library provides react wrapper components for implementing bootstrap elements.

  • react-router-dom: This is used for implementing client-side routing in React Applications

Let's add Bootstrap to our project. Open main.jsx and add the below import statement at the top.

import "bootstrap/dist/css/bootstrap.min.css";

Next, create a .env.local file and paste the keys we copied when we created the Supabase project in the above step.

VITE_SUPABASE_PROJECT_URL=<YOUR_PROJECT_URL_HERE>
VITE_SUPABASE_PROJECT_KEY=<YOUR_PROJECT_KEY_HERE>

It is perfectly fine to expose the URL and KEY in the project because they are only used to identify the project. Postgres' Row-Level-Security (RLS) is used to implement the security level checks. We can use RLS to set rules and restrict access to our Project keys.

Now let's initialize the Supabase client in our React app. Create a client.js file in the src/supabase directory.

import { createClient } from "@supabase/supabase-js";

const projectURL = import.meta.env.VITE_SUPABASE_PROJECT_URL;
const projectKey = import.meta.env.VITE_SUPABASE_PROJECT_KEY;

export const supabase = createClient(projectURL, projectKey);

Here, we are creating the Supabase client by passing our Project URL and the Project key and exporting the client instance that can be used elsewhere in the project.

Setting up React Context for User Management

Before implementing user Registration and Login, let's use React's Context API to handle state management throughout the App. Create a context folder inside src and add the AuthProvider.jsx file inside the context folder with the following content.

import { createContext, useContext, useState } from "react";

const AuthContext = createContext({});

export const useAuth = () => useContext(AuthContext);

const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  return (
    <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
  );
};

export default AuthProvider;

Here, we are just creating the boilerplate of the context API for state management and not adding any functionality. We will implement it later on in the course of this article. Also, we are creating and exporting the useAuth hook to use it later in our project.

Now open main.jsx and wrap the App component inside the AuthProvider.

import "bootstrap/dist/css/bootstrap.min.css";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import AuthProvider from "./context/AuthProvider";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>
);

By wrapping the entire App component inside the AuthProvider, we tell React to apply the Context to the whole of our App, so that React's Context API can manage all our App's state.

User Registration


NOTE

We will not explain the styling of our app as it's not in the scope of this article. We have added some basic styling using Bootstrap. It's up to you to style the components as you wish.


Now, let's move on to implementing the Registration functionality. Supabase provides many auth providers like Email, Phone, Google, GitHub, etc. By default, Email Auth is enabled, and you can check it by navigating Project -> Authentication -> Providers.

Supabase uses GoTrue as the underlying Auth mechanism and stores the user details in the users table in the auth schema. Create the Register.jsx file inside the src/pages directory and add the following code.

import { useRef, useState } from "react";
import { Alert, Button, Card, Form } from "react-bootstrap";
import { Link } from "react-router-dom";
import { supabase } from "../supabase/client";

const Register = () => {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const confirmPasswordRef = useRef(null);
  const [errorMsg, setErrorMsg] = useState("");
  const [msg, setMsg] = useState("");
  const [loading, setLoading] = useState(false);

  const register = (email, password) =>
    supabase.auth.signUp({ email, password });

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (
      !passwordRef.current?.value ||
      !emailRef.current?.value ||
      !confirmPasswordRef.current?.value
    ) {
      setErrorMsg("Please fill all the fields");
      return;
    }
    if (passwordRef.current.value !== confirmPasswordRef.current.value) {
      setErrorMsg("Passwords doesn't match");
      return;
    }
    try {
      setErrorMsg("");
      setLoading(true);
      const { data, error } = await register(
        emailRef.current.value,
        passwordRef.current.value
      );
      if (!error && data) {
        setMsg(
          "Registration Successful. Check your email to confirm your account"
        );
      }
    } catch (error) {
      setErrorMsg("Error in Creating Account");
    }
    setLoading(false);
  };

  return (
    <>
      <Card>
        <Card.Body>
          <h2 className="text-center mb-4">Register</h2>
          <Form onSubmit={handleSubmit}>
            <Form.Group id="email">
              <Form.Label>Email</Form.Label>
              <Form.Control type="email" ref={emailRef} required />
            </Form.Group>
            <Form.Group id="password">
              <Form.Label>Password</Form.Label>
              <Form.Control type="password" ref={passwordRef} required />
            </Form.Group>
            <Form.Group id="confirm-password">
              <Form.Label>Confirm Password</Form.Label>
              <Form.Control type="password" ref={confirmPasswordRef} required />
            </Form.Group>
            {errorMsg && (
              <Alert
                variant="danger"
                onClose={() => setErrorMsg("")}
                dismissible>
                {errorMsg}
              </Alert>
            )}
            {msg && (
              <Alert variant="success" onClose={() => setMsg("")} dismissible>
                {msg}
              </Alert>
            )}
            <div className="text-center mt-2">
              <Button disabled={loading} type="submit" className="w-50">
                Register
              </Button>
            </div>
          </Form>
        </Card.Body>
      </Card>
      <div className="w-100 text-center mt-2">
        Already a User? <Link to={"/login"}>Login</Link>
      </div>
    </>
  );
};

export default Register;

supabase.auth provides various APIs for different providers to implement signUp functionality. Here we are creating a form using react-bootstrap's Card component to make our form look like a card. We are getting an email and password from the user. We are using React's useRef hook to store the input values instead of the useState hook, as using refs avoids unnecessary re-renders whenever we enter the input. Whenever the user clicks the Register button, we disable the button using the loading state so multiple clicks can be avoided. When the user submits the form, the handleSubmit function is called, and we do some basic validations like checking whether the passwords match. Then we will call the supabase.auth's signUp function and pass the email and password. If the registration is successful, we will display the success message and ask the user to log in. If there is any error in the process, we will display the error message using the react-bootstrap's Alert component.

The UI would look like this,

Register Page

Once the registration process is successful, you will receive a mail to confirm your account. We need to configure the redirect URL to redirect the users to our site when they click on the link in the confirmation mail.

Go to the Supabase project Dashboard. Project -> Authentication -> URL Configuration. In that, set the site URL to http://localhost:5173. We redirect the user to the base URL once they confirm their account, and you may need to change the URL based on your configuration.

Auth URL Configration

Now, Open the email and confirm it. Only the confirmed users can log in. You can view the user's detail in the Supabase dashboard as well. Navigate to Supabase -> Your Project -> Authentication

User Login

Supabase provides signInWithPassword function to sign in the user with email and password. Before implementing the UI for the Login page, let's implement the login functionalities in AuthProvider.jsx. Also, supabase.auth has a function named onAuthStateChange, which is a listener and receives a notification every time an auth event occurs. Let's incorporate these functionalities into our Context Provider (AuthProvider). The updated AuthProvider.jsx will be as follows.

import { createContext, useContext, useEffect, useState } from "react";
import { supabase } from "../supabase/client";

const AuthContext = createContext({});

export const useAuth = () => useContext(AuthContext);

const login = (email, password) =>
  supabase.auth.signInWithPassword({ email, password });

const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [auth, setAuth] = useState(false);

  useEffect(() => {
    const { data } = supabase.auth.onAuthStateChange((event, session) => {
      if (event === "SIGNED_IN") {
        setUser(session.user);
        setAuth(true);
      }
    });
    return () => {
      data.subscription.unsubscribe();
    };
  }, []);

  return (
    <AuthContext.Provider value={{ user, login }}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthProvider;

The onAuthStateChange has a callback function that takes in two parameters, event and session. The event parameter can be any of the following values SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED, PASSWORD_RECOVERY. The session parameter contains details about the current session. For the login functionality, if the event is SIGNED_IN, we will update the user state and set the auth state to true. In the return block (cleanup block) of the useEffect hook, we unsubscribe from the auth listener. We will add all the auth functionalities in AUthProvider.jsx instead of adding them in various files.

Now, let's implement the login UI. Create Login.jsx inside src/pages and add the following content.

import { useRef, useState } from "react";
import { Alert, Button, Card, Form } from "react-bootstrap";
import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "../context/AuthProvider";

const Login = () => {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const [errorMsg, setErrorMsg] = useState("");
  const [loading, setLoading] = useState(false);
  const navigate = useNavigate();
  const { login } = useAuth();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      setErrorMsg("");
      setLoading(true);
      if (!passwordRef.current?.value || !emailRef.current?.value) {
        setErrorMsg("Please fill in the fields");
        return;
      }
      const {
        data: { user, session },
        error
      } = await login(emailRef.current.value, passwordRef.current.value);
      if (error) setErrorMsg(error.message);
      if (user && session) navigate("/");
    } catch (error) {
      setErrorMsg("Email or Password Incorrect");
    }
    setLoading(false);
  };

  return (
    <>
      <Card>
        <Card.Body>
          <h2 className="text-center mb-4">Login</h2>
          <Form onSubmit={handleSubmit}>
            <Form.Group id="email">
              <Form.Label>Email</Form.Label>
              <Form.Control type="email" ref={emailRef} required />
            </Form.Group>
            <Form.Group id="password">
              <Form.Label>Password</Form.Label>
              <Form.Control type="password" ref={passwordRef} required />
            </Form.Group>
            {errorMsg && (
              <Alert
                variant="danger"
                onClose={() => setErrorMsg("")}
                dismissible>
                {errorMsg}
              </Alert>
            )}
            <div className="text-center mt-2">
              <Button disabled={loading} type="submit" className="w-50">
                Login
              </Button>
            </div>
          </Form>
        </Card.Body>
        <div className="w-100 text-center mt-2">
          New User? <Link to={"/register"}>Register</Link>
        </div>
      </Card>
    </>
  );
};

export default Login;

Just like the Register component, we display a bootstrap form to get the email and password from the user. Once the user submits the form, we will make the login API call to Supabase. The signInWithPassword method returns a promise that we will resolve using async/await. The data attribute contains user and session, and the error attribute contains the error object. If the login is successful, the data object will contain the user and session details. If the session is null, the user hasn't confirmed their account. So we will check both the user and session objects and then redirect the user to the Home page. Else we will display the error message using the react-bootstrap's Alert component.

The Login page would look like this,

Login Page

Authenticated Routes

As we have implemented the Login route, let's create the Home page that can be accessed only when a user is logged in. In other words, an Authenticated Route. Let's create a Home.jsx page in the src/pages directory.

import React from "react";
import { useAuth } from "../context/AuthProvider";

const Home = () => {
  const { user } = useAuth();

  return <div>You are logged in and your email address is {user.email}</div>;
};

export default Home;

In this Home component, we get the user details from the Context using the useAuth hook and display the email address in the body.

Now, let's create a separate component to protect specific routes that can only be accessed by authenticated users. Create AuthRoute.jsx inside the src/components directory.

import { useAuth } from "../context/AuthProvider";
import { Navigate, Outlet, useLocation } from "react-router-dom";

const AuthRoute = () => {
  const { user } = useAuth();
  const location = useLocation();

  return user ? (
    <Outlet />
  ) : (
    <Navigate to={"/login"} replace state={{ path: location.pathname }} />
  );
};

export default AuthRoute;

In the above code, we are fetching the user from the Auth Context using the useAuth hook and checking if the user exists. If the user exists, we will direct the user to the protected component or redirect the user to the /login route using the <Navigate/> component. Here the <Outlet/> component acts as a placeholder where the component that matches the current route will be rendered.

Now, let's add all these routes in our App.jsx file.

import { Container } from "react-bootstrap";
import { Route, Routes } from "react-router-dom";
import Register from "./pages/Register";
import Login from "./pages/Login";
import AuthRoute from "./components/AuthRoute";
import Home from "./pages/Home";

const App = () => {
  return (
    <>
      <Container
        className="d-flex align-items-center justify-content-center"
        style={{ minHeight: "100vh" }}>
        <div className="w-100" style={{ maxWidth: "400px" }}>
          <Routes>
            <Route element={<AuthRoute />}>
              <Route path="/" element={<Home />} />
              <Route path="/home" element={<Home />} />
            </Route>
            <Route path="/register" element={<Register />} />
            <Route path="/login" element={<Login />} />
          </Routes>
        </div>
      </Container>
    </>
  );
};

export default App;

Here, we are wrapping our App inside react-bootstrap's Container, and using React router v6, we are setting up our routes. We are protecting the / and /home endpoints with the AuthRoute component ensuring only the authenticated users can use it. /login and /register routes are unprotected.

To enable client-side routing, we need to make one more change in main.jsx. Open main.jsx and update it like below

import "bootstrap/dist/css/bootstrap.min.css";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import AuthProvider from "./context/AuthProvider";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter>
      <AuthProvider>
        <App />
      </AuthProvider>
    </BrowserRouter>
  </React.StrictMode>
);

Here, we are wrapping our App component inside the BrowserRouter component of the react-router-dom. The BrowserRouter component provides the client-side routing functionalities in React.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data

OpenReplay

Happy debugging! Try using OpenReplay today.

Handling User Logout

Now, let's move on to implementing the logout functionality. Supabase provides a very simple method to sign out the user.

Open AuthProvider.jsx and add the following line.

const signOut = () => supabase.auth.signOut();

supabase.auth.signOut() method will remove the currently logged-in user from the browser.

Let's export this function in our context as well.

<AuthContext.Provider value={{ user, login, signOut }}>
  {children}
</AuthContext.Provider>

Also, as we have already seen, onAuthStateChange fires events when an auth event occurs. So, when the user logs out, the SIGNED_OUT event will be fired. Let's handle that as well inside the useEffect.

const { data } = supabase.auth.onAuthStateChange(async (event, session) => {
  if (event === "SIGNED_IN") {
    setUser(session.user);
    setAuth(true);
  } else if (event === "SIGNED_OUT") {
    setUser(null);
    setAuth(false);
  }

Here, we set the user state as null when the user signs out. So the context will become null and prevents the user from accessing protected routes once signed out.

The updated AuthProvider.jsx will be as follows.

import { createContext, useContext, useEffect, useState } from "react";
import { supabase } from "../supabase/client";

const AuthContext = createContext({});

export const useAuth = () => useContext(AuthContext);

const login = (email, password) =>
  supabase.auth.signInWithPassword({ email, password });

const signOut = () => supabase.auth.signOut();

const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [auth, setAuth] = useState(false);

  useEffect(() => {
    const { data } = supabase.auth.onAuthStateChange((event, session) => {
      if (event === "SIGNED_IN") {
        setUser(session.user);
        setAuth(true);
      } else if (event === "SIGNED_OUT") {
        setUser(null);
        setAuth(false);
      }
    });
    return () => {
      data.subscription.unsubscribe();
    };
  }, []);

  return (
    <AuthContext.Provider value={{ user, login, signOut }}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthProvider;

To make our app look more appealing, let's create a NavBar component and add the LogOut Button and other options.

import { Button } from "react-bootstrap";
import Container from "react-bootstrap/Container";
import Nav from "react-bootstrap/Nav";
import Navbar from "react-bootstrap/Navbar";
import { Link } from "react-router-dom";
import { useAuth } from "../context/AuthProvider";

const NavBar = () => {
  const { auth, signOut } = useAuth();

  const handleLogout = async (e) => {
    e.preventDefault();
    try {
      const { error } = await signOut();
      console.log(error);
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
      <Container>
        <Navbar.Brand>UserAuth</Navbar.Brand>
        <Navbar.Toggle aria-controls="responsive-navbar-nav" />
        <Navbar.Collapse id="responsive-navbar-nav">
          <Nav className="me-auto">
            {!auth && (
              <Nav.Link as={Link} to="/login">
                Login
              </Nav.Link>
            )}
            {!auth && (
              <Nav.Link as={Link} to="/register">
                Register
              </Nav.Link>
            )}
            {auth && (
              <Nav.Link as={Link} to="/">
                Home
              </Nav.Link>
            )}
          </Nav>
          <Nav>
            {auth && (
              <Nav.Link as={Button} onClick={handleLogout}>
                LogOut
              </Nav.Link>
            )}
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
};

export default NavBar;

We are using React bootstrap's Navbar component to create our NavBar component. We use the auth and signOut from the useAuth hook. We will display the options to the user based on the auth state. When the user clicks the Logout button, handleLogout will be called, and we will signOut the user.

Add the NavBar component in App.jsx above the routes because we need the NavBar to display across all the components. The Navbar will be displayed based on the user's auth status.

The Home page, along with the Navbar, would look like this,

Home Page

NavBar is displayed along with the LogOut button on the top right.

Handling Password Reset

Now, let's move on to implementing the Password Reset functionality. This will be useful if the user forgets their password and wants to reset it. The supabase.auth provides a resetPasswordForEmail function which can be used for resetting the password. Open AuthProvider.jsx and add the following code.

const passwordReset = (email) =>
  supabase.auth.resetPasswordForEmail(email, {
    redirectTo: "http://localhost:5173/update-password"
  });

// Add the passwordReset method to the context
<AuthContext.Provider value={{ auth, user, login, signOut, passwordReset }}>
  {children}
</AuthContext.Provider>;

Here, the redirectTo attribute lets Supabase redirect the user to the specified URL once they click on the password reset link sent in the mail. We are redirecting the user to the update-password route once they click on the link sent in the email.

Now let's design the UI for this. Create the PasswordReset component inside the src/pages directory and add the following code.

import { useRef, useState } from "react";
import { useAuth } from "../context/AuthProvider";
import { Alert, Button, Card, Form } from "react-bootstrap";
import { Link } from "react-router-dom";

const PasswordReset = () => {
  const { passwordReset } = useAuth();
  const emailRef = useRef(null);
  const [loading, setLoading] = useState(false);
  const [msg, setMsg] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      setLoading(true);
      const { data, error } = await passwordReset(emailRef.current.value);
      console.log(error);
      console.log(data);
      setMsg("Password reset has been sent to your email");
    } catch (e) {
      console.log(e);
    }
    setLoading(false);
  };

  return (
    <>
      <Card>
        <Card.Body>
          <h2 className="text-center mb-4">Login</h2>
          <Form onSubmit={handleSubmit}>
            <Form.Group id="email">
              <Form.Label>Email</Form.Label>
              <Form.Control type="email" ref={emailRef} required />
            </Form.Group>
            {msg && (
              <Alert variant="success" onClose={() => setMsg("")} dismissible>
                {msg}
              </Alert>
            )}
            <div className="text-center mt-2">
              <Button disabled={loading} type="submit" className="w-50">
                Send Reset Link
              </Button>
            </div>
          </Form>
        </Card.Body>
        <div className="w-100 text-center mt-2">
          Back to Login? <Link to={"/login"}>Login</Link>
        </div>
      </Card>
    </>
  );
};

export default PasswordReset;

Here we are rendering a simple form asking the user to enter their mail to send the password reset link. Once the user clicks on the button, the handleSubmit function will be invoked, and the password reset link will be sent to the email.

The UI would look like this,

Password Reset Screen

Let's add the route for this in App.jsx.

<Route path="/passwordreset" element={<PasswordReset />} />

And add the below line in Login.jsx at the end.

<div className="w-100 text-center mt-2">
  Forgot Password? <Link to={"/passwordreset"}>Click Here</Link>
</div>

When the user clicks on the link in the email, the SIGNED_IN and PASSWORD_RECOVERY auth events will be triggered. As we have already handled the SIGNED_IN event, let's listen to the PASSWORD_RECOVERY event in our AuthProvider component. When the PASSWORD_RECOVERY event is received, we will set the auth state to false.

import { createContext, useContext, useEffect, useState } from "react";
import { supabase } from "../supabase/client";

const AuthContext = createContext({});

export const useAuth = () => useContext(AuthContext);

const login = (email, password) =>
  supabase.auth.signInWithPassword({ email, password });

const signOut = () => supabase.auth.signOut();

const passwordReset = (email) =>
  supabase.auth.resetPasswordForEmail(email, {
    redirectTo: "http://localhost:5173/update-password"
  });

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState(false);
  const [user, setUser] = useState(null);

  useEffect(() => {
    const { data } = supabase.auth.onAuthStateChange(async (event, session) => {
      if (event == "PASSWORD_RECOVERY") {
        setAuth(false);
      } else if (event === "SIGNED_IN") {
        setUser(session.user);
        setAuth(true);
      } else if (event === "SIGNED_OUT") {
        setAuth(false);
        setUser(null);
      }
    });
    return () => {
      data.subscription.unsubscribe();
    };
  }, []);

  return (
    <AuthContext.Provider value={{ auth, user, login, signOut, passwordReset }}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthProvider;

Supabase provides a updateUser function where we can update the user details, including their password. Let's create the function to implement this.

const updatePassword = (updatedPassword) =>
  supabase.auth.updateUser({ password: updatedPassword });

Here we are updating the user's details with their new password. As simple as that. Let's export this function in our context as well.

<AuthContext.Provider
  value={{
    auth,
    user,
    login,
    signOut,
    passwordReset,
    updatePassword
  }}>
  {children}
</AuthContext.Provider>

Now, let's create the UpdatePassword component to let the user enter their new password and update it.

import React, { useRef, useState } from "react";
import { Alert, Button, Card, Form } from "react-bootstrap";
import { useAuth } from "../context/AuthProvider";
import { useNavigate } from "react-router-dom";

const UpdatePassword = () => {
  const { updatePassword } = useAuth();
  const passwordRef = useRef(null);
  const confirmPasswordRef = useRef(null);
  const [errorMsg, setErrorMsg] = useState("");
  const [loading, setLoading] = useState(false);
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!passwordRef.current?.value || !confirmPasswordRef.current?.value) {
      setErrorMsg("Please fill all the fields");
      return;
    }
    if (passwordRef.current.value !== confirmPasswordRef.current.value) {
      setErrorMsg("Passwords doesn't match. Try again");
      return;
    }
    try {
      setErrorMsg("");
      setLoading(true);
      const { data, error } = await updatePassword(passwordRef.current.value);
      if (!error) {
        navigate("/");
      }
    } catch (error) {
      setErrorMsg("Error in Updating Password. Please try again");
    }
    setLoading(false);
  };

  return (
    <>
      <Card>
        <Card.Body>
          <h2 className="text-center mb-4">Update Password</h2>
          <Form onSubmit={handleSubmit}>
            <Form.Group id="password">
              <Form.Label>Password</Form.Label>
              <Form.Control type="password" ref={passwordRef} required />
            </Form.Group>
            <Form.Group id="confirm-password">
              <Form.Label>Confirm Password</Form.Label>
              <Form.Control type="password" ref={confirmPasswordRef} required />
            </Form.Group>
            {errorMsg && (
              <Alert
                variant="danger"
                onClose={() => setErrorMsg("")}
                dismissible>
                {errorMsg}
              </Alert>
            )}
            <div className="text-center mt-2">
              <Button disabled={loading} type="submit" className="w-50">
                Update
              </Button>
            </div>
          </Form>
        </Card.Body>
      </Card>
    </>
  );
};

export default UpdatePassword;

Here we are rendering a form, getting the password and confirming password inputs from the user, and validating it. Then when the user clicks on the Update button, we call the updatePassword function and update the user's password. Once that's done, we redirect the user to the home page using the useNavigate method of react-router-dom.

The UI would look like this,

Update Password Screen

Handling Page Reload

Our app is almost complete except for one part. In the app we have created, once the user refreshes the page after logging in, it will ask the user to log in again. When the page reloads, the context gets reset, and the user state is not retained. One way to solve this is by saving the user info in localStorage and loading it when the page reloads. But Supabase provides an easy way to achieve this without using localStorage. The supabase.auth has a getUser method, which can retrieve the user from the session. We can make use of this method in our Context. In this way, we don't need to save our user info anywhere in our browser like localStorage, cookies, etc.,

In the AuthProvider component, add a new state called loading, which indicates the loading state of our app when we reload the page.

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

And add the below code in the useEffect block, just above the onAuthStateChanged listener, and add the loading state check in the Context Provider.

useEffect(() => {
  setLoading(true);
  const getUser = async () => {
    const { data } = await supabase.auth.getUser();
    const { user: currentUser } = data;
    setUser(currentUser ?? null);
    setLoading(false);
  };
  getUser();
  // onAuthStateChange code below
}, []);

return (
  <AuthContext.Provider
    value={{
      auth,
      user,
      login,
      signOut,
      passwordReset,
      updatePassword
    }}>
    {!loading && children}
  </AuthContext.Provider>
);

So, whenever the user reloads the page or opens the page after re-opening the browser, the useEffect hook will be invoked, and the getUser method will be called and fetches the user details from the supabase session using the supabase.auth.getUser() method and updates the user state. If the user is present, we will set the auth state to true; otherwise, false. Hence, we don't need to explicitly save the user's state in localStorage or cookies or anywhere in the browser.

That's it; the app is complete.

Conclusion

In this article, we have discussed how to implement user Authentication in React Applications using Supabase. We covered how to set up Supabase and implement its auth functionalities like registering, logging in, and resetting passwords. For further development, you can implement additional functionalities to rate-limit the number of password reset requests a user can make or build a new app on top of it. Overall, Supabase provides a simple and secure solution for adding user authentication to React Applications. With Supabase, developers can quickly implement user authentication and other backend functionalities without needing to manage servers or infrastructure.

The complete source code of this article can be found on my GitHub. If you find the code useful, please star the repo.

newsletter