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.
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>
);
}
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.
@jon-meyers, would you give an answer to the previous question, please?