+255
src/content/blog/supabase-auth-with-remix-and-vite.md
+255
src/content/blog/supabase-auth-with-remix-and-vite.md
···
1
+
---
2
+
title: Supabase Auth with Remix + Vite
3
+
description: Get up and running with Remix, Vite and the new Supabase SSR package
4
+
year: 2024
5
+
published_at: 2024-03-08
6
+
---
7
+
8
+
With Supabase now fully supporting doing authentication completely server-side, it has never been easier to take advantage of all of the features that it offers. We'll go through how to quickly spin up a Remix project and add supabase with authentication.
9
+
10
+
## Spin up a new Remix project
11
+
12
+
This guide should also work for non-vite Remix projects but my preference is to use vite so we'll use a clean vite template.
13
+
14
+
```bash
15
+
npx create-remix@latest --template remix-run/remix/templates/vite
16
+
```
17
+
18
+
Run this command in your terminal and follow the prompts, then open your new project in your editor of choice. You'll also need to [create a new project in supabase](https://supabase.com/) so that we can get access to the environment variables for this example.
19
+
20
+
Once you've created a supabase project, in the root of your project create a `.env` file and paste in the values for your `SUPABASE_URL` and `SUPABASE_ANON_KEY`. You can find these values by going into your supabase project dashboard, clicking on the on the connect button and selecting Remix from the frameworks list.
21
+
22
+

23
+
24
+

25
+
26
+
Lastly, install the `@supabase/ssr` package.
27
+
28
+
```bash
29
+
npm install @supabase/ssr
30
+
```
31
+
32
+
## Setting up authentication
33
+
34
+
There are a bunch of ways you can set up auth in supabase. E-mail and password combo, E-mail magic link, OAuth and so on. For the sake of this blog post we'll set up E-mail and password since it's the simplest one. Though that should be enough to explore the other methods as well if you wish.
35
+
36
+
### Create the necessary files
37
+
38
+
In here we are just creating a utility function so that we can re-use this function across all instances where we need to access supabase resources.
39
+
40
+
```ts
41
+
// app/utils/supabase.server.ts
42
+
43
+
import { createServerClient, serialize, parse } from "@supabase/ssr";
44
+
45
+
export function createClient(request: Request) {
46
+
const cookies = parse(request.headers.get("Cookie") ?? "");
47
+
const headers = new Headers();
48
+
49
+
const supabase = createServerClient(
50
+
process.env.SUPABASE_URL!,
51
+
process.env.SUPABASE_ANON_KEY!,
52
+
{
53
+
cookies: {
54
+
get(key) {
55
+
return cookies[key];
56
+
},
57
+
set(key, value, options) {
58
+
headers.append("Set-Cookie", serialize(key, value, options));
59
+
},
60
+
remove(key, options) {
61
+
headers.append("Set-Cookie", serialize(key, "", options));
62
+
},
63
+
},
64
+
}
65
+
);
66
+
67
+
return {
68
+
supabase,
69
+
headers,
70
+
};
71
+
}
72
+
```
73
+
74
+
This file is for when we are signing up for the first time and receive a confirmation e-mail. Clicking the link with log you in automatically but going forward you will log in through the log in page.
75
+
76
+
```ts
77
+
// app/routes/auth.callback.tsx
78
+
79
+
import { redirect, type LoaderFunctionArgs } from "@remix-run/node";
80
+
import { createClient } from "~/utils/supabase.server";
81
+
82
+
export async function loader({ request }: LoaderFunctionArgs) {
83
+
const requestUrl = new URL(request.url);
84
+
const code = requestUrl.searchParams.get("code");
85
+
const next = requestUrl.searchParams.get("next") || "/";
86
+
const { supabase, headers } = createClient(request);
87
+
88
+
if (code) {
89
+
const { error } = await supabase.auth.exchangeCodeForSession(code);
90
+
if (!error) {
91
+
return redirect(next, { headers });
92
+
}
93
+
}
94
+
95
+
return redirect("/auth/auth-error-page", { headers });
96
+
}
97
+
```
98
+
99
+
Sign up page, you will recieve a confirmation e-mail for the first time that you sign up. After you click the link in your e-mail you will be signed in.
100
+
101
+
```ts
102
+
// app/routes/signup.tsx
103
+
104
+
import { ActionFunctionArgs, json } from "@remix-run/node";
105
+
import { Form } from "@remix-run/react";
106
+
import { createClient } from "~/utils/supabase.server";
107
+
108
+
export async function action({ request }: ActionFunctionArgs) {
109
+
const formData = await request.formData();
110
+
const email = formData.get("email") as string;
111
+
const password = formData.get("password") as string;
112
+
const { supabase } = createClient(request);
113
+
114
+
const { error } = await supabase.auth.signUp({
115
+
email,
116
+
password,
117
+
});
118
+
119
+
if (error) {
120
+
return json({ message: error.message }, { status: 400 });
121
+
}
122
+
return null;
123
+
}
124
+
125
+
export default function Component() {
126
+
return (
127
+
<div>
128
+
<h1>Sign up</h1>
129
+
<Form method="POST">
130
+
<label htmlFor="email">E-mail</label>
131
+
<input type="email" name="email" id="email" />
132
+
<label htmlFor="password">Password</label>
133
+
<input type="password" name="password" id="password" />
134
+
<button type="submit">Sign up</button>
135
+
</Form>
136
+
</div>
137
+
);
138
+
}
139
+
```
140
+
141
+
Log in page, if all goes well then you will be redirected to the home page after logging in successfully.
142
+
143
+
```ts
144
+
// app/routes/login.tsx
145
+
146
+
import { ActionFunctionArgs, json } from "@remix-run/node";
147
+
import { Form } from "@remix-run/react";
148
+
import { redirect } from "react-router";
149
+
import { createClient } from "~/utils/supabase.server";
150
+
151
+
export async function action({ request }: ActionFunctionArgs) {
152
+
const formData = await request.formData();
153
+
const email = formData.get("email") as string;
154
+
const password = formData.get("password") as string;
155
+
const { supabase, headers } = createClient(request);
156
+
157
+
const { error } = await supabase.auth.signInWithPassword({
158
+
email,
159
+
password,
160
+
});
161
+
162
+
if (error) {
163
+
return json({ message: error.message }, { status: 400 });
164
+
}
165
+
return redirect("/", { headers });
166
+
}
167
+
168
+
export default function Component() {
169
+
return (
170
+
<div>
171
+
<h1>Log in</h1>
172
+
<Form method="POST">
173
+
<label htmlFor="email">E-mail</label>
174
+
<input type="email" name="email" id="email" />
175
+
<label htmlFor="password">Password</label>
176
+
<input type="password" name="password" id="password" />
177
+
<button type="submit">Log in</button>
178
+
</Form>
179
+
</div>
180
+
);
181
+
}
182
+
```
183
+
184
+
To verify that everything is working correctly, you can display the information of the currently logged in user.
185
+
186
+
```ts
187
+
// app/_index.tsx
188
+
189
+
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
190
+
import { useLoaderData } from "@remix-run/react";
191
+
import { createClient } from "~/utils/supabase.server";
192
+
193
+
export const meta: MetaFunction = () => {
194
+
return [
195
+
{ title: "New Remix App" },
196
+
{ name: "description", content: "Welcome to Remix!" },
197
+
];
198
+
};
199
+
200
+
export async function loader({ request }: LoaderFunctionArgs) {
201
+
const { supabase } = createClient(request);
202
+
203
+
const { data } = await supabase.auth.getSession();
204
+
205
+
return { user: data?.session?.user };
206
+
}
207
+
208
+
export default function Index() {
209
+
const { user } = useLoaderData<typeof loader>();
210
+
return (
211
+
<div>
212
+
<h1>Currently logged in user</h1>
213
+
<pre>{JSON.stringify(user, null, 2)}</pre>
214
+
</div>
215
+
);
216
+
}
217
+
```
218
+
219
+
Finally, add a log out button and resource route so that you can sign the user out.
220
+
221
+
```diff
222
+
export default function Index() {
223
+
const { user } = useLoaderData<typeof loader>();
224
+
return (
225
+
<div>
226
+
<h1>Currently logged in user</h1>
227
+
<pre>{JSON.stringify(user, null, 2)}</pre>
228
+
+ <Form method="POST" action="/logout">
229
+
+ <button type="submit">Log out</button>
230
+
+ </Form>
231
+
</div>
232
+
);
233
+
}
234
+
```
235
+
236
+
```ts
237
+
// app/logout.tsx
238
+
239
+
import { ActionFunctionArgs, redirect } from "@remix-run/node";
240
+
import { createClient } from "~/utils/supabase.server";
241
+
242
+
export async function action({ request }: ActionFunctionArgs) {
243
+
const { supabase, headers } = createClient(request);
244
+
await supabase.auth.signOut();
245
+
return redirect("/login", { headers });
246
+
}
247
+
248
+
export const loader = () => redirect("/");
249
+
```
250
+
251
+
And with that you have fully functioning authentication!
252
+
253
+
## Conclusion
254
+
255
+
This is all that is needed to set up authentication, you can explore the other authentication methods if you choose to do so. I have a repository set up with Github authentication that can be used as a starting point [which can be found here](https://github.com/nulfrost/supabase-remix-ssr).