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.
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
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;
};
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);
![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;
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>
)
Top comments (0)