Make Cookies the User Session Single Source of Truth with Supabase Auth Helpers

Share this video with your friends

Send Tweet

Remix Loaders allow us to fetch data server-side, before rendering a component. This works great if you are implementing your authorization rules for data fetching in the loader function, however, Supabase allows us to use Row Level Security policies to write access policies alongside the data in the database.

By default, supabase-js stores session data in localStorage, which exists only within the user's browser. If we want this session to be available within Loader or Action functions in Remix, we need to store the session in a cookie. Cookies are automatically sent with every request to the server.

In this lesson, we look at using the Supabase Auth Helpers package for Remix to automate this process, and swap out the storage mechanism for the Supabase client, to use cookies to store session data.

Additionally, we refactor our application to use the new createServerClient and createBrowserClient functions, making cookies the single source of truth about the user's current session, across the server and client-side of our Remix app.

Code Snippets

Install Remix Auth Helpers

npm i @supabase/auth-helpers-remix

Create server-side Supabase client

import { createServerClient } from "@supabase/auth-helpers-remix";

import type { Database } from "db_types";

export default ({
  request,
  response,
}: {
  request: Request;
  response: Response;
}) =>
  createServerClient<Database>(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_ANON_KEY!,
    { request, response }
  );

Create client-side Supabase client (simplified)

import { useState } from "react";
import { createBrowserClient } from "@supabase/auth-helpers-remix";

export const loader = async ({ request }: LoaderArgs) => {
  const env = {
    SUPABASE_URL: process.env.SUPABASE_URL!,
    SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
  };

  return json({ env });
};

const { env, session } = useLoaderData<typeof loader>();

const [supabase] = useState(() =>
  createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
);

return <Outlet context={{ supabase }} />;

Use Supabase in a loader

export const loader = async ({ request }: LoaderArgs) => {
  const response = new Response();
  const supabase = createServerSupabase({ request, response });

  const { data } = await supabase.from("messages").select();

  return json({ data }, { headers: response.headers });
};

Use Supabase in an action

export const action = async ({ request }: ActionArgs) => {
  const response = new Response();
  const supabase = createServerSupabase({ request, response });

  const { data } = await supabase.from("messages").insert({ content: "hello" });

  return json({ data }, { headers: response.headers });
};

Use Supabase in a component

import { useOutletContext } from "@remix-run/react";

import type { SupabaseOutletContext } from "~/root";

export default function Login() {
  const { supabase } = useOutletContext<SupabaseOutletContext>();

  const handleLogin = async () => {
    await supabase.auth.signInWithOAuth({
      provider: "github",
    });
  };

  return <button onClick={handleLogin}>Login</button>;
}

Resources