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. This is the user metadata saved, unencoded, to the local storage medium. It's unverified and can be tampered by the user, so don't use it for authorization or sensitive purposes.
Note that auth.getSession
reads the auth token and the unencoded session data from the local storage medium. It doesn't send a request back to the Supabase Auth server unless the local session is expired.
You should never trust the unencoded session data if you're writing server code, since it could be tampered with by the sender. If you need verified, trustworthy user data, call auth.getUser
instead, which always makes a request to the Auth server to fetch trusted data.
_10const user = session?.user
Or, if you need trusted user data, you can call the getUser()
function, which retrieves the trusted user data by making a request to the Supabase Auth server.
_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})