Supabase Auth with Remix
We generally recommend using the new @supabase/ssr
package instead of auth-helpers
. @supabase/ssr
takes the core concepts of the Auth Helpers package and makes them available to any server framework. Check out the migration doc to learn more.
This submodule provides convenience helpers for implementing user authentication in Remix applications.
For a complete implementation example, check out this free egghead course or this GitHub repo.
Install the Remix helper library#
This library supports the following tooling versions:
- Remix:
>=1.7.2
Set up environment variables#
Retrieve your project URL and anon key in your project's API settings in the Dashboard to set up the following environment variables. For local development you can set them in a .env
file. See an example.
Code Exchange route#
The Code Exchange
route is required for the server-side auth flow implemented by the Remix Auth Helpers. It exchanges an auth code
for the user's session
, which is set as a cookie for future requests made to Supabase.
Create a new file at app/routes/auth.callback.jsx
and populate with the following:
Server-side#
The Supabase client can now be used server-side - in loaders and actions - by calling the createServerClient
function.
Loader#
Loader functions run on the server immediately before the component is rendered. They respond to all GET requests on a route. You can create an authenticated Supabase client by calling the createServerClient
function and passing it your SUPABASE_URL
, SUPABASE_ANON_KEY
, and a Request
and Response
.
_25import { json } from '@remix-run/node' // change this import to whatever runtime you are using_25import { createServerClient } from '@supabase/auth-helpers-remix'_25_25export const loader = async ({ request }) => {_25 const response = new Response()_25 // an empty response is required for the auth helpers_25 // to set cookies to manage auth_25_25 const supabaseClient = createServerClient(_25 process.env.SUPABASE_URL,_25 process.env.SUPABASE_ANON_KEY,_25 { request, response }_25 )_25_25 const { data } = await supabaseClient.from('test').select('*')_25_25 // in order for the set-cookie header to be set,_25 // headers must be returned as part of the loader response_25 return json(_25 { data },_25 {_25 headers: response.headers,_25 }_25 )_25}
Supabase will set cookie headers to manage the user's auth session, therefore, the
response.headers
must be returned from theLoader
function.
Action#
Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the createServerClient
function and passing it your SUPABASE_URL
, SUPABASE_ANON_KEY
, and a Request
and Response
.
_21import { json } from '@remix-run/node' // change this import to whatever runtime you are using_21import { createServerClient } from '@supabase/auth-helpers-remix'_21_21export const action = async ({ request }) => {_21 const response = new Response()_21_21 const supabaseClient = createServerClient(_21 process.env.SUPABASE_URL,_21 process.env.SUPABASE_ANON_KEY,_21 { request, response }_21 )_21_21 const { data } = await supabaseClient.from('test').select('*')_21_21 return json(_21 { data },_21 {_21 headers: response.headers,_21 }_21 )_21}
Supabase will set cookie headers to manage the user's auth session, therefore, the
response.headers
must be returned from theAction
function.
Session and user#
You can determine if a user is authenticated by checking their session using the getSession
function.
_10const {_10 data: { session },_10} = await supabaseClient.auth.getSession()
The session contains a user property.
_10const user = session?.user
Or, if you don't need the session, you can call the getUser()
function.
_10const {_10 data: { user },_10} = await supabaseClient.auth.getUser()
Client-side#
We still need to use Supabase client-side for things like authentication and realtime subscriptions. Anytime we use Supabase client-side it needs to be a single instance.
Creating a singleton Supabase client#
Since our environment variables are not available client-side, we need to plumb them through from the loader.
These may not be stored in
process.env
for environments other than Node.
Next, we call the useLoaderData
hook in our component to get the env
object.
We then want to instantiate a single instance of a Supabase browser client, to be used across our client-side components.
And then we can share this instance across our application with Outlet Context.
Syncing server and client state#
Since authentication happens client-side, we need to tell Remix to re-call all active loaders when the user signs in or out.
Remix provides a hook useRevalidator
that can be used to revalidate all loaders on the current route.
Now to determine when to submit a post request to this action, we need to compare the server and client state for the user's access token.
Let's pipe that through from our loader.
And then use the revalidator, inside the onAuthStateChange
hook.
Check out this repo for full implementation example
Authentication#
Now we can use our outlet context to access our single instance of Supabase and use any of the supported authentication strategies from supabase-js
.
Subscribe to realtime events#
Ensure you have enabled replication on the table you are subscribing to.
Migration guide#
Migrating to v0.2.0#
PKCE Auth flow#
PKCE is the new server-side auth flow implemented by the Remix Auth Helpers. It requires a new loader
route for /auth/callback
that exchanges an auth code
for the user's session
.
Check the Code Exchange Route steps above to implement this route.
Authentication#
For authentication methods that have a redirectTo
or emailRedirectTo
, this must be set to this new code exchange API Route - /api/auth/callback
. This is an example with the signUp
function:
_10supabaseClient.auth.signUp({_10 email: 'jon@example.com',_10 password: 'sup3rs3cur3',_10 options: {_10 emailRedirectTo: 'http://localhost:3000/auth/callback',_10 },_10})