# RBAC & Private Routes in Next.js with Supabase using NextShield.

Hello everyone! Hope you liked the  [previous post](https://blog.imjulian.com/rbac-and-private-routes-in-nextjs). Today we are going to build a Supabase example with NextShield, so let's get started.

![sweet dreams](https://cdn.hashnode.com/res/hashnode/image/upload/v1633614589667/db5LONWSl.gif)

## Create a new Supabase project.

Go to  [supabase website](https://supabase.io/)  and click on "Start your project":

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633572218920/-pXCamGWI.png)

Sign in and click on "New Project":

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633572367869/5shF50ZfB.png)

Fill the form and create the project:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633572504955/UTDygYDla.png)

Wait until the project is created:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633572611215/zqInPrVP9.png)

### Configure Supabase.

We need to configure supabase to satisfy our needs with RBAC, so go to the SQL menu and click on new query:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633574196477/QkOUgbxVX.png)

Create the profiles table:

```sql
create table public.profiles (
  id uuid references auth.users not null,
  role text,

  primary key (id)
);
``` 

And enable row level security to restrict the access:

```sql
alter table public.profiles enable row level security;
```

Then add the following policies to grant user access to their data:

```sql
create policy "Users can insert their own profile."
  on profiles for insert
  with check ( auth.uid() = id );

create policy "Users can update own profile."
  on profiles for update
  using ( auth.uid() = id );
```

Finally, create a trigger to insert a new record when a new user is created:

```sql
-- inserts a row into public.profiles
create function public.handle_new_user() 
returns trigger 
language plpgsql 
security definer 
as $$
begin
  insert into public.profiles (id, role)
  values (new.id, "EMPLOYEE");
  return new;
end;
$$;

-- 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();
```

**For a real project, you want this trigger to be well-tested because if it fails it could block the user sign ups.**

## Create a new Next.js Project:

To avoid writing boilerplate code you can use the project that we wrote in the [previous post](https://blog.imjulian.com/rbac-and-private-routes-in-nextjs), but if you didn't follow that one, you can just [clone the repo (clone the main branch)](https://github.com/imjulianeral/nextshield-examples).

### Install Dependencies.

```shell
npm i next-shield @supabase/supabase-js valtio
``` 

### Change the styles.

Replace the code under `styles/globals.css` with:

```css
* {
  box-sizing: border-box;
}

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  background-color: black;
  color: white;
}

nav {
  display: flex;
  flex-direction: column;
}

@media (min-width: 768px) {
  nav {
    flex-direction: row;
    justify-content: space-evenly;
  }
}

a {
  display: block;
  text-align: center;
  text-decoration: none;
  color: white;
  padding: 1rem;
  transition: all ease-in-out 0.3s;
}

a:hover {
  color: black;
  background-color: #3cce8d;
}

button {
  padding: 0.5rem 1rem;
  margin: 0.5rem;
  cursor: pointer;
  background-color: white;
  color: black;
  border: none;
  font-weight: 700;
  font-size: 1.2rem;
  transition: all ease-in-out 0.3s;
}

button:hover {
  background-color: #3cce8d;
}

.center {
  height: 40vh;
  display: grid;
  place-items: center;
  text-align: center;
}

@media (min-width: 768px) {
  .center {
    height: 90vh;
  }
}

.loading {
  margin: 40vh auto;
}
```

### Setup Supabase in Next.js.

Create a file in the root of the project called `.env.local` with the following values:

```env
NEXT_PUBLIC_SUPABASE_URL=""
NEXT_PUBLIC_SUPABASE_KEY=""
```

Then go to your supabase project, scroll down and search for those values:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633573633424/cPslL8Ko4.png)

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633573774991/DV_4RBTs1.png)

Create a folder called `db` with the file `client.ts`:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633573299019/JyUzR_xOv.png)

And Initialize the client

```ts
import { createClient } from '@supabase/supabase-js'

// Create a single supabase client for interacting with your database
export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL as string,
  process.env.NEXT_PUBLIC_SUPABASE_KEY as string
)
``` 

## Supabase Auth Provider.

Supabase gives you a way of having OAuth2 by providing the secret keys of the auth provider that you're using (Google, Twitter, GitHub, etc.), you can use whatever you want in this example even passwordless auth, in my case I used the google provider, to get the keys you can see the following tutorial:

[Get the Google keys](https://youtu.be/_XM9ziOzWk4)

## Define the User types.

Go to the file `types/User.ts` and replace its content with:

```ts
export interface UserProfile {
  id: string
  role: string
}

export interface Auth {
  isAuth: boolean
  isLoading: boolean
  user: UserProfile | null | undefined
}

export type AuthStore = {
  updateAuth: (params: Auth) => void
} & Auth
``` 

## Store.

Create a store folder with an `index.ts` file:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633575529525/UJr3aprc3.png)

Then we are going to use the `valtio` state manager for our store, with the following code:

```ts
import { proxy } from 'valtio'

import { supabase } from '@/db/client'
import type { AuthStore } from '@/types/User'

export const authStore = proxy<AuthStore>({
  user: undefined,
  isAuth: !!supabase.auth.user(),
  isLoading: false,
  updateAuth({ isAuth, isLoading, user }) {
    authStore.isAuth = isAuth
    authStore.isLoading = isLoading
    authStore.user = user
  },
})
```

That's it, the main reason for using `valtio` is the simplicity of usage and the improvements that it has to avoid unnecessary rerenders.

## `useAuth` Hook.

Create a folder called hooks with an `auth.ts` file:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1633575896912/DKvCQR4Bs.png)

Create a function called useAuth:

```ts
export function useAuth() {}
```

Inside of it use the `useSnapshot` to access to our store:

```ts
const { updateAuth, ...state } = useSnapshot(authStore)
```

Then define the sign in & out functions:

```ts
  const signIn = useCallback(async () => {
    await supabase.auth.signIn({ provider: 'google' })
  }, [])

  const signOut = useCallback(async () => {
    await supabase.auth.signOut()
  }, [])
```

After that add an `useEffect` and pass the `updateAuth` method as the only dependency:

```ts
  useEffect(() => {

  }, [updateAuth])
```

Then create a function called `getUserProfile` to query the user profile:

```ts
const getUserProfile = async () => {
      const { data: profile } = await supabase
        .from<UserProfile>('profiles')
        .select('id, role')
        .single()

      if (profile) {
        updateAuth({
          user: profile,
          isAuth: true,
          isLoading: false,
        })

        return
      }

      updateAuth({
        user: null,
        isAuth: false,
        isLoading: false,
      })
    }
```

The reason why we are not using a filter to get a specific user is because we already defined in our policies that each user owns their data and nobody else.

After defining this function, execute it to make a request when the component mounts and also execute it inside the `onAuthStateChange` to request the user's data whenever the auth state changes:

```ts
getUserProfile()

const { data } = supabase.auth.onAuthStateChange(() => {
   getUserProfile()
})
```
And also create a subscription to get realtime data:

```ts
const profile = supabase
      .from<UserProfile>('profiles')
      .on('UPDATE', payload => {
        updateAuth({
          user: payload.new,
          isAuth: true,
          isLoading: false,
        })
      })
      .subscribe()
```

And finally unsubscribe when the component unmounts:

```ts
    return () => {
      data?.unsubscribe()
      supabase.removeSubscription(profile)
    }
```

Then just return the functions that we created alongside the state:

```ts
return {
    signIn,
    signOut,
    ...state,
  }
```

## Update the pages.

In `dashboard.tsx`, `users/index.tsx` and `users/[id].tsx` add the sign out:

```jsx
import { Layout } from '@/components/routes/Layout'
import { useAuth } from '@/hooks/auth'

export default function Dashboard() {
  const { signOut } = useAuth()

  return (
    <Layout title="Dashboard">
      <h1>Dashboard</h1>

      <button onClick={signOut}>Sign Out</button>
    </Layout>
  )
}
```

Add the sign in to the `login.tsx` page:

```jsx
import { Layout } from '@/components/routes/Layout'
import { useAuth } from '@/hooks/auth'

export default function Login() {
  const { signIn } = useAuth()

  return (
    <Layout title="Login">
      <h1>Login</h1>
      <button onClick={signIn}>Sign In</button>
    </Layout>
  )
}
```

Add the `changeRole` functionality in the `profile.tsx` page:

```jsx
import { useCallback } from 'react'
import { Layout } from '@/components/routes/Layout'
import { supabase } from '@/db/client'
import { useAuth } from '@/hooks/auth'

export default function Profile() {
  const { signOut, user } = useAuth()
  const role = user?.role === 'EMPLOYEE' ? 'ADMIN' : 'EMPLOYEE'

  const changeRole = useCallback(async () => {
    await supabase.from('profiles').update({ role })
  }, [role])

  return (
    <Layout title="Profile">
      <h1>Profile</h1>
      <button onClick={signOut}>Sign Out</button>
      <button onClick={changeRole}>Change my user role to {role}</button>
    </Layout>
  )
}
```

Add  [`ComponentShield`](https://imjulianeral.github.io/next-shield/docs/protect-components/ComponentShield)  in the pricing page:

```jsx
import { ComponentShield } from 'next-shield'
import { Layout } from '@/components/routes/Layout'
import { useAuth } from '@/hooks/auth'

export default function Pricing() {
  const { user, isAuth, isLoading } = useAuth()

  return (
    <Layout title="Pricing">
      <h1>Pricing</h1>

      <ComponentShield showIf={isAuth && !isLoading}>
        <p>You are authenticated</p>
      </ComponentShield>
      <ComponentShield RBAC showForRole="ADMIN" userRole={user?.role}>
        <p>You are an ADMIN</p>
      </ComponentShield>
      <ComponentShield RBAC showForRole="EMPLOYEE" userRole={user?.role}>
        <p>You are an EMPLOYEE</p>
      </ComponentShield>
    </Layout>
  )
}
```

## Combine Supabase and NextShield.

Go to the `Shield.tsx` component, import the `useAuth` hook, and replace the values.

```ts
const { user, isAuth, isLoading } = useAuth()

const shieldProps: NextShieldProps<
    ['/profile', '/dashboard', '/users', '/users/[id]'],
    ['/', '/login']
  > = {
    router,
    isAuth,
    isLoading,
    privateRoutes: ['/profile', '/dashboard', '/users', '/users/[id]'],
    publicRoutes: ['/', '/login'],
    hybridRoutes: ['/pricing'],
    loginRoute: '/login',
    LoadingComponent: <Loading />,
    RBAC: {
      ADMIN: {
        grantedRoutes: ['/dashboard', '/profile', '/users', '/users/[id]'],
        accessRoute: '/dashboard',
      },
      EMPLOYEE: {
        grantedRoutes: ['/profile', '/dashboard'],
        accessRoute: '/profile',
      },
    },
    userRole: user?.role, // Must be undefined when isAuth is false & defined when is true
  }
```

## Result.

As an unauthenticated user, you only can access public and hybrid routes:

![unauth.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1633579671192/yUGnMbBQr.gif)

And when you're authenticated, you only can access hybrid and private routes where you have granted access:

![auth.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1633580572274/S6cIS95GU.gif)

That's it, really simple, you can check the  [github repo](https://github.com/imjulianeral/nextshield-examples/tree/supabase) to download this example, and also don't forget to read the [docs](https://imjulianeral.github.io/next-shield/).

![surprised.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1633621858885/9KVcWsXhW.gif)

Hope you like it, I'm going to publish an example per week with the most popular auth providers (Clerk, Auth0, etc.), so if you don't wanna miss any article please follow me ;D.

### See you next time!

![brainexplode](https://cdn.hashnode.com/res/hashnode/image/upload/v1633621603452/z09_yFVo-I.gif)


