Files
supabase/examples/auth/quickstarts/react/src/App.jsx
T
Chris Chinchilla 398480a3cd docs: Update react auth quickstart to use getClaims and move all code to examples folder (#42279)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a new React authentication quickstart example demonstrating
email-based OTP (magic link) authentication flow with Supabase
integration.

* **Documentation**
* Updated the React auth quickstart guide to use dynamic code samples
for improved maintainability.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-30 12:06:05 +11:00

156 lines
4.6 KiB
React

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_DEFAULT_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>
);
}