@auth/supabase-adapter
Official Supabase adapter for Auth.js / NextAuth.js.
Installationβ
- npm
- Yarn
- pnpm
npm install @supabase/supabase-js @auth/supabase-adapter
yarn add @supabase/supabase-js @auth/supabase-adapter
pnpm add @supabase/supabase-js @auth/supabase-adapter
SupabaseAdapter()β
SupabaseAdapter(options): Adapter
This adapter is developed by the community and not officially maintained or supported by Supabase. It uses the Supabase Database to store user and session data in a separate next_auth
schema. It is a standalone Auth server that does not interface with Supabase Auth and therefore provides a different feature set.
If you're looking for an officially maintained Auth server with additional features like built-in email server, phone auth, and Multi Factor Authentication (MFA / 2FA), please use Supabase Auth with the Auth Helpers for Next.js.
Setupβ
Configure Auth.jsβ
Add this adapter to your pages/api/[...nextauth].js
next-auth configuration object.
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@auth/supabase-adapter"
// For more information on each option (and a full list of options) go to
// https://authjs.dev/reference/core#authconfig
export default NextAuth({
// https://authjs.dev/reference/core/providers
providers: [...],
adapter: SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
// ...
})
Create the NextAuth schema in Supabaseβ
Setup your database as described in our main schema, by copying the SQL schema below in the Supabase SQL Editor.
Alternatively you can select the NextAuth Quickstart card on the SQL Editor page, or create a migration with the Supabase CLI.
--
-- Name: next_auth; Type: SCHEMA;
--
CREATE SCHEMA next_auth;
GRANT USAGE ON SCHEMA next_auth TO service_role;
GRANT ALL ON SCHEMA next_auth TO postgres;
--
-- Create users table
--
CREATE TABLE IF NOT EXISTS next_auth.users
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
name text,
email text,
"emailVerified" timestamp with time zone,
image text,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_unique UNIQUE (email)
);
GRANT ALL ON TABLE next_auth.users TO postgres;
GRANT ALL ON TABLE next_auth.users TO service_role;
--- uid() function to be used in RLS policies
CREATE FUNCTION next_auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Create sessions table
--
CREATE TABLE IF NOT EXISTS next_auth.sessions
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
expires timestamp with time zone NOT NULL,
"sessionToken" text NOT NULL,
"userId" uuid,
CONSTRAINT sessions_pkey PRIMARY KEY (id),
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.sessions TO postgres;
GRANT ALL ON TABLE next_auth.sessions TO service_role;
--
-- Create accounts table
--
CREATE TABLE IF NOT EXISTS next_auth.accounts
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
type text NOT NULL,
provider text NOT NULL,
"providerAccountId" text NOT NULL,
refresh_token text,
access_token text,
expires_at bigint,
token_type text,
scope text,
id_token text,
session_state text,
oauth_token_secret text,
oauth_token text,
"userId" uuid,
CONSTRAINT accounts_pkey PRIMARY KEY (id),
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.accounts TO postgres;
GRANT ALL ON TABLE next_auth.accounts TO service_role;
--
-- Create verification_tokens table
--
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
(
identifier text,
token text,
expires timestamp with time zone NOT NULL,
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
CONSTRAINT token_unique UNIQUE (token),
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
);
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;
Expose the NextAuth schema in Supabaseβ
Expose the next_auth
schema via the Serverless API in the API settings by adding next_auth
to the "Exposed schemas" list.
When developing locally add next_auth
to the schemas
array in the config.toml
file in the supabase
folder that was generated by the Supabase CLI.
Advanced usageβ
Enabling Row Level Security (RLS)β
Postgres provides a powerful feature called Row Level Security (RLS) to limit access to data.
This works by sending a signed JWT to your Supabase Serverless API. There is two steps to make this work with NextAuth:
Generate the Supabase access_token
JWT in the session callbackβ
To sign the JWT use the jsonwebtoken
package:
- npm
- Yarn
- pnpm
npm install jsonwebtoken
yarn add jsonwebtoken
pnpm add jsonwebtoken
Using the NexthAuth.js Session callback create the Supabase access_token
and append it to the session
object.
To sign the JWT use the Supabase JWT secret which can be found in the API settings
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@auth/supabase-adapter"
import jwt from "jsonwebtoken"
// For more information on each option (and a full list of options) go to
// https://authjs.dev/reference/configuration/auth-options
export default NextAuth({
// https://authjs.dev/reference/core/providers
providers: [...],
adapter: SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
callbacks: {
async session({ session, user }) {
const signingSecret = process.env.SUPABASE_JWT_SECRET
if (signingSecret) {
const payload = {
aud: "authenticated",
exp: Math.floor(new Date(session.expires).getTime() / 1000),
sub: user.id,
email: user.email,
role: "authenticated",
}
session.supabaseAccessToken = jwt.sign(payload, signingSecret)
}
return session
},
},
// ...
})
Inject the Supabase access_token
JWT into the clientβ
For example, given the following public schema:
-- Note: This table contains user data. Users should only be able to view and update their own data.
create table users (
-- UUID from next_auth.users
id uuid not null primary key,
name text,
email text,
image text,
constraint "users_id_fkey" foreign key ("id")
references next_auth.users (id) match simple
on update no action
on delete cascade -- if a user is deleted in NextAuth they will also be deleted in our public table.
);
alter table users enable row level security;
create policy "Can view own user data." on users for select using (next_auth.uid() = id);
create policy "Can update own user data." on users for update using (next_auth.uid() = id);
-- This trigger automatically creates a user entry when a new user signs up via NextAuth.
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.users (id, name, email, image)
values (new.id, new.name, new.email, new.image);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on next_auth.users
for each row execute procedure public.handle_new_user();
The supabaseAccessToken
is now available on the session
object and can be passed to the supabase-js client. This works in any environment: client-side, server-side (API routes, SSR), as well as in middleware edge functions!
// ...
// Use `useSession()` or `unstable_getServerSession()` to get the NextAuth session.
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
Usage with TypeScriptβ
You can pass types that were generated with the Supabase CLI to the Supabase Client to get enhanced type safety and auto-completion.
Creating a new supabase client object:
import { createClient } from "@supabase/supabase-js"
import { Database } from "../database.types"
const supabase = createClient<Database>()
Extend the session type with the supabaseAccessToken
β
In order to extend the session
object with the supabaseAccessToken
we need to extend the session
interface in a types/next-auth.d.ts
file:
import NextAuth, { DefaultSession } from "next-auth"
declare module "next-auth" {
// Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
interface Session {
// A JWT which can be used as Authorization header with supabase-js for RLS.
supabaseAccessToken?: string
user: {
// The user's postal address
address: string
} & DefaultSession["user"]
}
}
Parametersβ
βͺ options: SupabaseAdapterOptions
Returnsβ
Adapter
SupabaseAdapterOptionsβ
This is the interface of the Supabase adapter options.
Propertiesβ
secretβ
secret: string;
The secret to grant access to the database
urlβ
url: string;
The URL of your Supabase database