Use Supabase Auth with React
Learn how to use Supabase Auth with React.js.
Quickstart#
Launch a new project in the Supabase Dashboard.
Your new database has a table for storing your users. You can see that this table is currently empty by running some SQL in the SQL Editor.
select * from auth.users;Create a React app using a Vite template.
npm create vite@latest my-app -- --template reactNavigate to the React app and install the Supabase libraries.
cd my-app && npm install @supabase/supabase-jsRename .env.example to .env.local and populate with your Supabase connection variables:
Get API details#
To interact with data in database tables, you use the client libraries that wrap the auto-generated Data API endpoints, authenticating using the Project URL and key from the project Connect dialog.
Read the API keys docs for a full explanation of all key types, their uses, and where to find them.
VITE_SUPABASE_URL=your-project-urlVITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key-or-anon-keyExplore drop-in UI components for your Supabase app.
UI components built on shadcn/ui that connect to Supabase via a single command.
In App.jsx, create a Supabase client using your Project URL and key.
The code uses the getClaims method in App.jsx to validate the local JWT before showing the signed-in user.
import './index.css'import { useState, useEffect } from 'react'import { createClient } from '@supabase/supabase-js'const supabase = createClient( import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY)export default function App() { const [loading, setLoading] = useState(false) const [email, setEmail] = useState('') const [claims, setClaims] = useState(null) // Check URL params on initial render const params = new URLSearchParams(window.location.search) const hasTokenHash = params.get('token_hash') const [verifying, setVerifying] = useState(!!hasTokenHash) const [authError, setAuthError] = useState(null) const [authSuccess, setAuthSuccess] = useState(false) useEffect(() => { // Check if we have token_hash in URL (magic link callback) const params = new URLSearchParams(window.location.search) const token_hash = params.get('token_hash') const type = params.get('type') if (token_hash) { // Verify the OTP token supabase.auth .verifyOtp({ token_hash, type: type || 'email', }) .then(({ error }) => { if (error) { setAuthError(error.message) } else { setAuthSuccess(true) // Clear URL params window.history.replaceState({}, document.title, '/') } setVerifying(false) }) } // Check for existing session using getClaims supabase.auth.getClaims().then(({ data: { claims } }) => { setClaims(claims) }) // Listen for auth changes const { data: { subscription }, } = supabase.auth.onAuthStateChange(() => { supabase.auth.getClaims().then(({ data: { claims } }) => { setClaims(claims) }) }) return () => subscription.unsubscribe() }, []) const handleLogin = async (event) => { event.preventDefault() setLoading(true) const { error } = await supabase.auth.signInWithOtp({ email, options: { emailRedirectTo: window.location.origin, }, }) if (error) { alert(error.error_description || error.message) } else { alert('Check your email for the login link!') } setLoading(false) } const handleLogout = async () => { await supabase.auth.signOut() setClaims(null) } // Show verification state if (verifying) { return ( <div> <h1>Authentication</h1> <p>Confirming your magic link...</p> <p>Loading...</p> </div> ) } // Show auth error if (authError) { return ( <div> <h1>Authentication</h1> <p>ā Authentication failed</p> <p>{authError}</p> <button onClick={() => { setAuthError(null) window.history.replaceState({}, document.title, '/') }} > Return to login </button> </div> ) } // Show auth success (briefly before claims load) if (authSuccess && !claims) { return ( <div> <h1>Authentication</h1> <p>ā Authentication successful!</p> <p>Loading your account...</p> </div> ) } // If user is logged in, show welcome screen if (claims) { return ( <div> <h1>Welcome!</h1> <p>You are logged in as: {claims.email}</p> <button onClick={handleLogout}>Sign Out</button> </div> ) } // Show login form return ( <div> <h1>Supabase + React</h1> <p>Sign in via magic link with your email below</p> <form onSubmit={handleLogin}> <input type="email" placeholder="Your email" value={email} required={true} onChange={(e) => setEmail(e.target.value)} /> <button disabled={loading}> {loading ? <span>Loading</span> : <span>Send magic link</span>} </button> </form> </div> )}Before proceeding, change the email template to support a server-side authentication flow that sends a token hash:
- Go to the Auth templates page in your dashboard.
- Select the Confirm sign up template.
- Change
{{ .ConfirmationURL }}to{{ .SiteURL }}?token_hash={{ .TokenHash }}&type=email. - Change your Site URL to
https://localhost:5173
Start the app, go to http://localhost:5173 in a browser, and open the browser console and you should be able to register and log in.
npm run dev