Auth

Use Supabase Auth with React

Learn how to use Supabase Auth with React.js.


1

Create a new Supabase project

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.

SQL_EDITOR
1
select * from auth.users;
2

Create a React app

Create a React app using a Vite template.

Terminal
1
npm create vite@latest my-app -- --template react
3

Install the Supabase client library

Navigate to the React app and install the Supabase libraries.

Terminal
1
cd my-app && npm install @supabase/supabase-js
4

Declare Supabase Environment Variables

Rename .env.example to .env.local and populate with your Supabase connection variables:

Project URL
Publishable key
Anon key
.env.local
1
VITE_SUPABASE_URL=your-project-url
2
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=your-publishable-key-or-anon-key
View source

You can also get the Project URL and key from the project's Connect dialog.

Read the API keys docs for a full explanation of all key types and their uses.

5

Set up your login component

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.

src/App.jsx
1
import "./index.css";
2
import { useState, useEffect } from "react";
3
import { createClient } from "@supabase/supabase-js";
4
5
const supabase = createClient(import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY);
6
7
export default function App() {
8
const [loading, setLoading] = useState(false);
9
const [email, setEmail] = useState("");
10
const [claims, setClaims] = useState(null);
11
12
// Check URL params on initial render
13
const params = new URLSearchParams(window.location.search);
14
const hasTokenHash = params.get("token_hash");
15
16
const [verifying, setVerifying] = useState(!!hasTokenHash);
17
const [authError, setAuthError] = useState(null);
18
const [authSuccess, setAuthSuccess] = useState(false);
19
20
useEffect(() => {
21
// Check if we have token_hash in URL (magic link callback)
22
const params = new URLSearchParams(window.location.search);
23
const token_hash = params.get("token_hash");
24
const type = params.get("type");
25
26
if (token_hash) {
27
// Verify the OTP token
28
supabase.auth.verifyOtp({
29
token_hash,
30
type: type || "email",
31
}).then(({ error }) => {
32
if (error) {
33
setAuthError(error.message);
34
} else {
35
setAuthSuccess(true);
36
// Clear URL params
37
window.history.replaceState({}, document.title, "/");
38
}
39
setVerifying(false);
40
});
41
}
42
43
// Check for existing session using getClaims
44
supabase.auth.getClaims().then(({ data: { claims } }) => {
45
setClaims(claims);
46
});
47
48
// Listen for auth changes
49
const {
50
data: { subscription },
51
} = supabase.auth.onAuthStateChange(() => {
52
supabase.auth.getClaims().then(({ data: { claims } }) => {
53
setClaims(claims);
54
});
55
});
56
57
return () => subscription.unsubscribe();
58
}, []);
59
60
const handleLogin = async (event) => {
61
event.preventDefault();
62
setLoading(true);
63
const { error } = await supabase.auth.signInWithOtp({
64
email,
65
options: {
66
emailRedirectTo: window.location.origin,
67
}
68
});
69
if (error) {
70
alert(error.error_description || error.message);
71
} else {
72
alert("Check your email for the login link!");
73
}
74
setLoading(false);
75
};
76
77
const handleLogout = async () => {
78
await supabase.auth.signOut();
79
setClaims(null);
80
};
81
82
// Show verification state
83
if (verifying) {
84
return (
85
<div>
86
<h1>Authentication</h1>
87
<p>Confirming your magic link...</p>
88
<p>Loading...</p>
89
</div>
90
);
91
}
92
93
// Show auth error
94
if (authError) {
95
return (
96
<div>
97
<h1>Authentication</h1>
98
<p>āœ— Authentication failed</p>
99
<p>{authError}</p>
100
<button
101
onClick={() => {
102
setAuthError(null);
103
window.history.replaceState({}, document.title, "/");
104
}}
105
>
106
Return to login
107
</button>
108
</div>
109
);
110
}
111
112
// Show auth success (briefly before claims load)
113
if (authSuccess && !claims) {
114
return (
115
<div>
116
<h1>Authentication</h1>
117
<p>āœ“ Authentication successful!</p>
118
<p>Loading your account...</p>
119
</div>
120
);
121
}
122
123
// If user is logged in, show welcome screen
124
if (claims) {
125
return (
126
<div>
127
<h1>Welcome!</h1>
128
<p>You are logged in as: {claims.email}</p>
129
<button onClick={handleLogout}>
130
Sign Out
131
</button>
132
</div>
133
);
134
}
135
136
// Show login form
137
return (
138
<div>
139
<h1>Supabase + React</h1>
140
<p>Sign in via magic link with your email below</p>
141
<form onSubmit={handleLogin}>
142
<input
143
type="email"
144
placeholder="Your email"
145
value={email}
146
required={true}
147
onChange={(e) => setEmail(e.target.value)}
148
/>
149
<button disabled={loading}>
150
{loading ? <span>Loading</span> : <span>Send magic link</span>}
151
</button>
152
</form>
153
</div>
154
);
155
}
View source
6

Customize email template

Before proceeding, change the email template to support 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
7

Start the app

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.

Terminal
1
npm run dev