DEV Community

Cover image for TLDR; Google Auth in ReactJS using Supabase

TLDR; Google Auth in ReactJS using Supabase

My project was created using vite + react-js. I have google auth enabled in my Supabase project with Client IDs etc, already configured from Google Cloud on Supabase already.

Google client IDs and client secret already configured

As I tested on localhost so I have the URL whitelisted in my Supabase project as well:

Below is how I'll implement it in my ReactJS project.

Install this first:

yarn add @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode

Add a file AuthContext.tsx in your lib folder, and add this code:

import { createContext, useContext, useEffect, useState } from "react";
import type { ReactNode } from "react";
import type { Session, User } from "@supabase/supabase-js";
import { supabase } from "@/lib/supabaseClient";
import { toast } from "sonner";

type UserProfile = {
  name: string;
  email: string;
  avatarUrl: string;
};

type AuthContextValue = {
  user: User | null;
  session: Session | null;
  profile: UserProfile | null;
  isLoading: boolean;
  signInWithGoogle: () => Promise<void>;
  signOut: () => Promise<void>;
};

const AuthContext = createContext<AuthContextValue | undefined>(undefined);

const extractProfile = (user: User | null): UserProfile | null => {
  if (!user) {
    return null;
  }

  const metadata = user.user_metadata ?? {};
  const name =
    metadata.full_name ||
    metadata.name ||
    metadata.user_name ||
    (user.email ? user.email.split("@")[0] : "Anonymous");

  return {
    name,
    email: user.email ?? metadata.email ?? "",
    avatarUrl: metadata.avatar_url || metadata.picture || "",
  };
};

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [session, setSession] = useState<Session | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isAuthenticating, setIsAuthenticating] = useState(false);

  useEffect(() => {
    const initSession = async () => {
      const { data, error } = await supabase.auth.getSession();
      if (error) {
        console.error("Failed to get session:", error);
      }
      setSession(data.session ?? null);
      setIsLoading(false);
    };

    void initSession();

    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_event, nextSession) => {
      setSession(nextSession);
      setIsLoading(false);
      setIsAuthenticating(false);
    });

    return () => {
      subscription.unsubscribe();
    };
  }, []);

  const signInWithGoogle = async () => {
    try {
      setIsAuthenticating(true);
      const { error } = await supabase.auth.signInWithOAuth({
        provider: "google",
        options: {
          redirectTo: window.location.origin,
        },
      });
      if (error) {
        setIsAuthenticating(false);
        toast.error("Unable to sign in with Google.");
        console.error("Error signing in:", error);
        return;
      }
      setIsAuthenticating(false);
    } catch (error) {
      setIsAuthenticating(false);
      console.error("Unexpected error signing in:", error);
      toast.error("Unexpected error signing in.");
    }
  };

  const signOut = async () => {
    try {
      setIsAuthenticating(true);
      const { error } = await supabase.auth.signOut();
      if (error) {
        setIsAuthenticating(false);
        toast.error("Unable to sign out.");
        console.error("Error signing out:", error);
        return;
      }
      setIsAuthenticating(false);
      toast.success("Signed out.");
    } catch (error) {
      setIsAuthenticating(false);
      console.error("Unexpected error signing out:", error);
      toast.error("Unexpected error signing out.");
    }
  };

  const user = session?.user ?? null;
  const value: AuthContextValue = {
    user,
    session,
    profile: extractProfile(user),
    isLoading: isLoading || isAuthenticating,
    signInWithGoogle,
    signOut,
  };

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

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
};

Enter fullscreen mode Exit fullscreen mode

Add another file named supabaseClient.ts and in it add:

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

const supabaseUrl = import.meta.env.YOUR_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.YOUR_SUPABASE_ANON_KEY;

if (!supabaseUrl || !supabaseAnonKey) {
  throw new Error("Missing Supabase environment variables. Please set YOUR_PUBLIC_SUPABASE_URL and YOUR_SUPABASE_ANON_KEY.");
}

export const supabase = createClient(supabaseUrl, supabaseAnonKey);
Enter fullscreen mode Exit fullscreen mode

![Supabase ublic project URL

Make sure you have the YOUR_PUBLIC_SUPABASE_URL you can find from Data API as shown in the image above, and YOUR_SUPABASE_ANON_KEY which would be in API Keys option in Project Settings of Supabase and have them both stored in the .env file of your project.

Now you are finally ready to add the changes to your App.tsx. Wrap everything around <AuthProvider>, meaning AuthProvider will be most parent component.

//...other imports
import { AuthProvider } from "@/lib/AuthContext";

//... other code

const App = () => (

<AuthProvider>
//... all your other code will be the child of AuthProvider
</AuthProvider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

Now let's say you're logged in and you want to change the UI to say user's display image. Here is the UI example of that, for SomeScreenHeader.tsx:

import { useAuth } from "@/lib/AuthContext";
//... other imports

const SomeScreenHeader = () => {

const {
    user,
    profile,
    isLoading: isAuthLoading,
    signInWithGoogle,
    signOut,
  } = useAuth();

//... other code

return (
<div className="flex items-center justify-between gap-4">

{user && profile ? (
              <div>
                <p>Welcome, {profile.name}!</p>
                <p>{profile.email}</p>
                <p>{profile.avatarUrl}</p>
                <button onClick={signOut}>Sign Out</button>
              </div>
            ) : (
               <button onClick={() => {
                  void signInWithGoogle();
                }}>Login with Google</button>
            )}

</div>
)

Enter fullscreen mode Exit fullscreen mode

Top comments (0)