Supabase is an open-source Firebase alternative known for providing a backend with a real-time database, authentication, and APIs. In other words, it's a powerhouse giving you (almost) everything you need for your backend.
However, while Supabase offers powerful authentication mechanisms, if your system is complex enough, you'll reach a point where you'll need to complement this with robust authorization controls to manage what authenticated users can (or can't) access or modify.
And before you start planning the development of such system (I know what you're thinking!), take a moment to breathe and consider that Cerbos is the perfect complement to Supabase, bringing those advanced authorization capabilities that are missing.
Let's explore how you can seamlessly combine these two without breaking a sweat.
In case you accidentally stumbled into this article and are wondering what Cerbos is, here's a quick rundown.
Cerbos is an open-source, policy-based authorization engine that decouples your authorization logic from your application's code. This makes it easy to define, manage, and enforce complex access control policies at scale.
In simple terms, Cerbos simplifies how you manage who gets to do what within your application.
Want more details? Check out Cerbos’ GitHub project or explore Cerbos Hub to get a managed solution.
To demonstrate how to combine Supabase’s authentication and Cerbos’ authorization, we’ll create a simple set of API routes in a Node.js project.
Start by initializing a Node.js project and installing the required dependencies:
mkdir supabase-cerbos-auth
cd supabase-cerbos-auth
cd supabase-cerbos-auth
npm init -y
npm install @supabase/supabase-js @cerbos/grpc jsonwebtoken express
With those commands, you’ve set up a new project and installed the Supabase client, Cerbos gRPC client, JWT for handling tokens, and Express for setting up our API routes.
First, create a file named config.js to initialize the Supabase client:
// config.js
import { createClient } from '@supabase/supabase-js';
const SUPABASE_URL = 'https://your-project.supabase.co';
const SUPABASE_KEY = 'your_anon_key';
export const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
Make sure to replace the SUPABASE_URL
and SUPABASE_KEY
with your actual project’s details from the Supabase dashboard.
For this, you'll want to go to your Project's configuration page, and disable the confirmation email & secure email change functionalities.
This will let us create our users programmatically with the following code:
seeds.js
import { supabase } from './config.js';
const { data, error } = await supabase.auth.signUp({
email: 'myadmin@email.com',
password: 'admin-password',
options: {
data: {
user_role: "admin",
},
},
})
if (error) {
console.log(error)
}
const { data_user, error_user } = await supabase.auth.signUp({
email: 'myuser@email.com',
password: 'user-password',
options: {
data: {
user_role: "user",
},
},
})
if (error_user) {
console.log(error_user)
}
Running this script will create the two users we need for our tests.
Now, let's define two routes in our Express app:
Here’s the code:
server.js
import express from 'express';
import { supabase } from './config.js';
import { GRPC } from '@cerbos/grpc';
import jwt from 'jsonwebtoken';
const app = express();
const PORT = 3000;
const cerbos = new GRPC('localhost:3592', { tls: false });
app.use(express.json());
// Login route
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
console.log("Sign in data: ", data)
if (error) {
return res.status(403).json({ error: 'Invalid credentials' });
}
// Create a JWT token with the user data
const token = jwt.sign({ userId: data.user.id }, 'secret_key');
res.json({ user: data.user, token: data.session.access_token });
});
// Secured route
app.get('/secured', async (req, res) => {
// Get the token from the Authorization header
const token = req.headers?.authorization?.split(' ')[1];
try {
// Use Supabase to get the user data
const { data: { user }, error } = await supabase.auth.getUser(token);
if (error || !user) {
console.log(error);
return res.status(401).json({ error: 'Unauthorized' });
}
//We're definig access to a resource of type "document" with an ID of "123"
const payload = {
resource: {
kind: 'document',
id: '123',
policyVersion: 'default',
},
principal: {
id: user.id,
roles: [user.user_metadata.user_role],
},
action: 'read'
}
const decision = await cerbos.isAllowed(payload);
console.log(decision)
if (decision) {
return res.json({ message: 'You have access to this secured route!' });
} else {
return res.status(403).json({ error: 'Forbidden' });
}
} catch (error) {
console.error(error);
return res.status(401).json({ error: 'Unauthorized' });
}
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Let's now create a policy that will only let admins do everything with documents, and only allow users to read them.
Make sure to put the policy file inside a policies
folder:
policies/document.yaml
# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/Policy.schema.json
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
resource: document
rules:
- actions:
- create
- read
- update
- delete
effect: EFFECT_ALLOW
roles:
- admin
- actions:
- read
effect: EFFECT_ALLOW
roles:
- user
We now have to run Cerbos locally, for that we can use the following Docker command:
docker run --rm --name cerbos -t \
-v $(pwd)/policies:/policies \
-p 3592:3592 \
ghcr.io/cerbos/cerbos:latest server
Or if you're using PowerShell on Windows:
docker run --rm --name cerbos -t -v "${PWD}/policies:/policies" -p 3592:3592 ghcr.io/cerbos/cerbos:latest server
First off, let's start your server with:
node server.js
Log in to create a session
curl -X POST http://localhost:3000/login -H "Content-Type: application/json" -d '{"email": "myadmin@email.com", "password": "admin-password"}'
This command should return a JSON in the response body. That JSON should have a property called "token", keep the value of that field, because we'll be using it in a second.
Access the secured route
curl -X GET http://localhost:3000/secured -H "Authorization: Bearer <your token>"
Supabase will keep the session open on its side, so you don't need to send anything extra.
With Supabase handling your authentication needs and Cerbos streamlining authorization, you’ve got yourself a scalable and secure setup in just a few minutes.
Supabase’s integration with Cerbos means you no longer need to worry about writing complex access control logic.
Using Cerbos with Supabase isn’t just a time-saver—it’s a way to build secure applications with maintainable policies.
Are you looking to level up your AuthZ game? Give Cerbos’ open-source solution - Cerbos PDP - a try.
Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team
Join thousands of developers | Features and updates | 1x per month | No spam, just goodies.