Implement Authentication for Supabase with OAuth and Github

Share this video with your friends

Send Tweet

Supabase supports a collection of auth strategies - Email and password, passwordless and OAuth. In this lesson, we look at implementing OAuth with GitHub.

In order to do this, we create a new GitHub OAuth app, and configure Supabase to use its Client ID and Secret. Additionally, we create a <Login /> component that uses the Supabase client to sign users in and out.

This identifies a problem that we don't have access to the environment variables required to create a Supabase client outside of loaders or actions. Therefore, we pipe through our SUPABASE_URL and SUPABASE_ANON_KEY from the Loader function, and create a singleton Supabase client, to use across our components.

Lastly, we look at sharing this single instance of Supabase through the Outlet Context and declare types to ensure we have TypeScript helping us out throughout the application.

Code Snippets

Expose environment variables from the server

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

  return json({ env });
};

Access env in component

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

Create a singleton Supabase client

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

Share global variables with Outlet Context

<Outlet context={{ supabase }} />

Consuming Outlet Context

const { supabase } = useOutletContext<SupabaseOutletContext>();

Logging in with GitHub

await supabase.auth.signInWithOAuth({
  provider: "github",
});

Logging out

await supabase.auth.signOut();

Entire root component

import { json, LoaderArgs, MetaFunction } from "@remix-run/node";
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLoaderData,
} from "@remix-run/react";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { useState } from "react";

import type { Database } from "db_types";

type TypedSupabaseClient = SupabaseClient<Database>;

export type SupabaseOutletContext = {
  supabase: TypedSupabaseClient;
};

export const meta: MetaFunction = () => ({
  charset: "utf-8",
  title: "New Remix App",
  viewport: "width=device-width,initial-scale=1",
});

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

  return json({ env });
};

export default function App() {
  const { env } = useLoaderData<typeof loader>();

  const [supabase] = useState(() =>
    createClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
  );
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet context={{ supabase }} />
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

Resources

ed leach
ed leach
~ a year ago

Wow - all those steps. And it worked! I'm a little behind on my typescript. Can you explain this:

type TypedSupabaseClient = SupabaseClient<Database>

Is this assigning or adding the <Database> type to the supabaseClient object?

Is this supabase syntax or typescript syntax? If it is typescript syntax can you point me to some documentation where it is described? Thanks.

~ a year ago

@jon-meyers, would you give an answer to the previous question, please?