Skip to content

Latest commit

 

History

History
380 lines (280 loc) · 11.9 KB

auth.mdx

File metadata and controls

380 lines (280 loc) · 11.9 KB
id title description
auth
Auth
Use Supabase to Authenticate and Authorize your users.

User Management

Supabase makes it simple to manage your users.

When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a profiles table referencing the user using a user_id field.

Supabase provides the routes to sign up, log in, log out, and manage users in your apps and websites.

Third Party Logins

We currently support the following OAuth providers:

  • Google
  • GitHub
  • GitLab
  • Azure
  • Facebook
  • Bitbucket

OAuth Logins.

You can enable providers by navigating to Authentication > Settings > External OAuth Providers and inputting your Client ID and Secret for each.

To fetch these, you need to:

  1. Generate Client ID and Secret (Google, GitHub, GitLab, Bitbucket).
  2. Enter Authorized Redirect URI: https://<your-project>.supabase.co/auth/v1/callback on provider dashboard.

Row Level Security

Authentication only gets you so far. When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS). Supabase makes it simple to turn RLS on and off.

Policies

Policies are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.

With policies, your database becomes the rules engine. Instead of repetitively filtering your queries, like this ...

const loggedInUserId = 'd0714948'
let { data, error } = await supabase
    .from('users')
    .select('user_id, name')
    .eq('user_id', loggedInUserId)

// console.log(data)
// => { id: 'd0714948', name: 'Jane' }

... you can simply define a rule on your database table, auth.uid() = user_id, and your request will return the rows which pass the rule, even when you remove the filter from your middleware:

let user = await supabase
    .from('users')
    .select('user_id, name')

// console.log(data)
// Still => { id: 'd0714948', name: 'Jane' }

Policies Are Like WHERE Clauses

Policies are easy to understand once you get the hang of them. You can just think of them as adding a WHERE clause to every query. For example if you had a policy like this:

create policy "Individuals can view their own todos." on todos for
    select using (auth.uid() = user_id);

It would translate to this whenever a user tries to select from the todos table:

select *
from todos
where auth.uid() = todos.user_id; -- Policy is implicitly added.

How It Works

  1. A user signs up. Supabase creates a new user in the auth.users table.
  2. Supabase returns a new JWT, which contains the user's UUID.
  3. Every request to your database also sends the JWT.
  4. Postgres inspects the JWT to determine the user making the request.
  5. The user's UID can be used in policies to restrict access to rows.

Supabase provides a special function in Postgres, auth.uid(), which extracts the user's UID from the JWT. This is especially useful when creating policies.

Policy Examples

Here are some examples to show you the power of PostgreSQL's RLS. Each policy is attached to a table, and the policy is executed every time a the table is accessed.

Allow read access

-- 1. Create table
create table profiles (
  id uuid references auth.users,
  avatar_url text
);

-- 2. Enable RLS
alter table profiles 
  enable row level security;

-- 3. Create Policy
create policy "Public profiles are viewable by everyone." 
  on profiles for select using (
    true
  );
  1. Creates a table called profiles in the public schema (default schema).
  2. Enables Row Level Security.
  3. Creates a policy which allows all select queries to run.

Restrict updates

-- 1. Create table
create table profiles (
  id uuid references auth.users,
  avatar_url text
);

-- 2. Enable RLS
alter table profiles 
  enable row level security;

-- 3. Create Policy
create policy "Users can update their own profiles." 
  on profiles for update using (
    auth.uid() = id
  );
  1. Creates a table called profiles in the public schema (default schema).
  2. Enables RLS.
  3. Creates a policy which allows logged in users to update their own data.

Policies with joins

Policies can even include table joins. This example shows how you can query "external" tables to build more advanced rules.

create table teams (
  id serial primary key,
  name text
);

-- 2. Create many to many join
create table members (
  team_id bigint references teams,
  user_id uuid references auth.users
);

-- 3. Enable RLS
alter table teams 
  enable row level security;

-- 4. Create Policy
create policy "Team members can update team details if they belong to the team."
  on teams
  for update using (
    auth.uid() in ( 
      select user_id from members 
      where team_id = id 
    )
  );

Policies with security definer functions

Policies can also make use of security definer functions. This is useful in a many-to-many relationship where you want to restrict access to the linking table. Following the teams and members example from above, this example shows how you can use security definer function in combination with a policy to control access to the members table.

-- 1. Follow example for 'Policies with joins' above

-- 2.  Enable RLS
alter table members 
  enable row level security

-- 3.  Create security definer function
create or replace function get_teams_for_user(user_id uuid)
returns setof bigint as $$
    select team_id 
    from members 
    where user_id = $1
$$ stable language sql security definer;

-- 4. Create Policy
create policy "Team members can update team members if they belong to the team."
  on members
  for all using (
    team_id in (
      select get_teams_for_user(auth.uid())
    )
  );

Verifying email domains

Postgres has a function right(string, n) that returns the rightmost n characters of a string. You could use this to match staff member's email domains.

-- 1. Create table
create table leaderboard (
  id uuid references auth.users,
  high_score bigint
);

-- 2. Enable RLS
alter table leaderboard 
  enable row level security;

-- 3. Create Policy
create policy "Only Blizzard staff can update leaderboard"
  on leaderboard
  for update using (
    right(auth.email(), 13) = '@blizzard.com'
  );

Advanced policies

Use the full power of SQL to build extremely advanced rules.

In this example, we will create a posts and comments database and then create a policy that depends on another policy. (In this case, the comments policy depends on the posts policy.)

create table posts (
  id            serial    primary key, 
  creator_id    uuid      not null     references auth.users(id), 
  title         text      not null, 
  body          text      not null, 
  publish_date  date      not null     default now(), 
  audience      uuid[]    null -- many to many table omitted for brevity
);

create table comments (
  id            serial    primary key, 
  post_id       int       not null     references posts(id)  on delete cascade, 
  user_id       uuid      not null     references auth.users(id), 
  body          text      not null, 
  comment_date  date      not null     default now()
);

create policy "Creator can see their own posts"
on posts
for select
using (
  auth.uid() = posts.creator_id
);

create policy "Logged in users can see the posts if they belong to the post 'audience'."
on posts
for select
using (
  auth.uid() = any (posts.audience)
);

create policy "Users can see all comments for posts they have access to."
on comments
for select
using (
  exists (
    select 1 from posts
    where posts.id = comments.post_id
  )
);

Tips

You don't have to use policies

You can also put your authorization rules in your middleware, similar to how you would create security rules with any other backend <-> middleware <-> frontend architecture.

Policies are a tool. In the case of "serverless/Jamstack" setups, they are especially effective because you don't have to deploy any middleware at all.

However, if you want to use another authorization method for your applications, that's also fine. Supabase is "just Postgres", so if your application works with Postgres, then it also works with Supabase.

Tip: Make sure to enable RLS for all your tables, so that your tables are inaccessible. Then use the "Service" which we provide, which is designed to bypass RLS.

Check authentication settings on Supabase

Navigate to Authentication > Settings on app.supabase.io, and you'll be able to change settings for things like:

  • SITE URL, which is used for determining where to redirect users after they confirm their email addresses or attempt to use a magic link to log in.
  • Disabling email confirmations.
  • Enabling external OAuth providers, such as Google and GitHub.

Never use a service key on the client

Supabase provides special "Service" keys, which can be used to bypass all RLS. These should never be used in the browser or exposed to customers, but they are useful for administrative tasks.

Create a public.users table

Even though Supabase provides an auth.users table, it is helpful to also create a users table in the public schema, which uses the same UID Primary Key as the auth.users. For security purposes, the auth schema is not exposed on the auto-generated API. Creating a public.users table allows you to interact via the Supabase client – especially useful for cross-table queries.

Pro tip: If you want to add a row to your public.users table every time a user signs up, you can use triggers. For example:

-- inserts a row into public.users
create function public.handle_new_user() 
returns trigger as $$
begin
  insert into public.users (id)
  values (new.id);
  return new;
end;
$$ language plpgsql security definer;

-- trigger the function every time a user is created
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();

Disable realtime for private tables

Our realtime server doesn't provide per-user security. Until we build a more robust auth system for WebSockets, you can disable realtime functionality for any private tables. To do this, you can manage the underlying Postgres replication publication:

/**
 * REALTIME SUBSCRIPTIONS
 * Only allow realtime listening on public tables.
 */

begin; 
  -- remove the realtime publication
  drop publication if exists supabase_realtime; 

  -- re-create the publication but don't enable it for any tables
  create publication supabase_realtime;  
commit;

-- add a table to the publication
alter publication supabase_realtime add table products;

-- add other tables to the publication
alter publication supabase_realtime add table posts;

We're in the process of building enhanced realtime security.

Next Steps