Topics

How we use Supabase RLS in business applications

  • column

Hello, this is Matsun from Liberogic.

In our previous article, we introduced a web app with authentication built on Supabase Auth and Cloudflare. This time, we continue from there and take a closer look at **RLS (Row Level Security)**, which we touched on briefly.

With Supabase Auth, you can

confirm who is logged in

. However, authentication alone is not enough for business applications.

"Which data this person is allowed to view"
"which data they are allowed to change"

. This is where RLS comes in.
This article explains how to use Supabase RLS in business applications.

What Is RLS?

RLS stands for Row Level Security, a mechanism that controls access at the level of individual rows in a database.
For example, suppose a database holds application data like this.

申請ID, 申請者, 所属会社, 所属拠点, 申請内容, ステータス

All users' requests are stored in the same table, however,

  • when a general employee logs in, they should only see their own requests.
  • A supervisor should be able to see requests from their own department or office.
  • A headquarters administrator should be able to see all requests.

RLS handles this kind of control on the database side, and it is an important feature in business applications.

Auth and RLS serve different roles

As I mentioned in my previous article, Supabase Auth and RLS serve different roles.
Here is how the two roles relate to each other.

Supabase Auth → 誰なのか確認する
RLS → その人がどのデータにアクセスできるか決める

For example, suppose Mr. Morimoto logs in. Supabase Auth verifies that

ユーザーID:12345

is the user. RLS then uses that user ID to

申請者ID = 12345

retrieve only that user's data.
Even if the same table contains 10,000 request records, this setup ensures Mr. Morimoto can only see his own requests.

The easiest pattern to understand is "only the user's own data"

With RLS, the easiest pattern to picture is one that shows only the user's own data.

For example, in a member-only account page,

  • their own registration details
  • their own application history
  • their own contract details
  • their own inquiry history
  • the files they uploaded

is all that gets displayed.

The application also specifies the user ID when fetching data, but rather than relying on that alone, the database enforces the rule that **"only the logged-in user's own data" can be accessed**.
It's important to enforce this at both the application and database levels.
Even if the frontend implementation contains a mistake, RLS ensures that another user's data cannot be retrieved.

Split by company

In partner portals and similar applications, data is sometimes displayed per company rather than per user.

For example,

A株式会社
 ├ 森本さん
 ├ 大塚さん
 └ 山田さん

For example, if there are three users, all three can view projects belonging to Company A, but none can view projects belonging to Company B. As for the data structure,

ユーザー → 所属会社 → 案件

we set up that relationship.
Then, on the RLS side, users can only access data belonging to the company they are logged in under.
This is a common setup for partner portals: even when dozens or hundreds of companies use the same web application, the data each user sees changes depending on the company they log in with.

Split by branch office

As business applications grow more sophisticated, branch offices come into play alongside companies. For example,

本部

東京支店
 ├ 担当者A
 └ 担当者B

大阪支店
 ├ 担当者C
 └ 担当者D

is the structure.

  • Staff at the Tokyo branch can only see Tokyo branch data.
  • Staff at the Osaka branch can only see Osaka branch data.
  • Headquarters can view everything.

This is a common structure in business applications such as store management, branch management, and agency management. From here,

ユーザー → 会社 → 拠点 → 役割 → アクセスできるデータ

leads to a permission design, and in business applications this permission design becomes especially important.

Administrators can see everything

Of course, there are also administrator roles. For example,

  • General user
  • Site administrator
  • Head Office Administrator
  • System Administrator

permissions are separated as follows.

  • General users can access their own data.
  • Branch administrators can access their own branch.
  • Head office administrators can access all branches.
  • System administrators can also manage users, among other things.

That is the general structure.

一般ユーザー → 自分だけ
拠点管理者 → 所属拠点
本部管理者 → 全拠点

We also build these rules into RLS, not just the app.
Hiding a menu item in the admin screen does not control access, so the data itself must be made inaccessible.

Control not only whether users can "view" data, but also whether they can "change" it.

RLS is not only for controlling read access.

For example, in a request application,

general employees can:

  • Create their own requests
  • Edit their own drafts
  • Not modify approved requests

Managers can:

  • View requests submitted by their subordinates
  • Approve or return

Headquarters administrators can

  • view all records

. In other words, the rules look like this:

見る・登録する・変更する・削除する

Each of these needs to be considered separately. Access control is not simply about "this person can see this data." It also includes how that data should be handled in day-to-day operations as part of the permission design.

You need to design operation permissions not only for viewing, but also for creating and updating records.

Don't manage permissions only on the app side

For example, on the front end,

if 管理者 → 管理ボタンを表示

is a common approach, but controlling what is displayed on screen alone is not enough.

  • Even if a button is hidden in the browser, what happens if someone calls the API directly?
  • What if someone directly specifies the URL?
  • What if someone specifies a different user ID to fetch data?

This is a concern that comes up. For that reason, in practice,

画面
↓
アプリケーション
↓
API
↓
RLS
↓
Database

we verify access at multiple layers.
RLS plays the role of protecting data at the deepest layer, closest to the database.
In business applications, the key is not to rely on data being "hidden on screen," but to ensure that the data simply cannot be retrieved in the first place.

RLS alone does not solve everything

Conversely, there is no need to try to handle all permission control with RLS alone.

For example,

  • Should this screen be accessible?
  • Should this API be executed?
  • Should this button be displayed?
  • Before executing this process, verify that it has been approved.

The application itself also makes these decisions.

If you're using Cloudflare Workers, the Workers side may also handle communication with external APIs and server-side processing.
So in practice,

Cloudflare
↓
Next.js
↓
Supabase Auth
↓
アプリケーション側の権限確認
↓
RLS
↓
Database

it often looks something like this.
Rather than trying to handle everything in one place, we protect what needs protecting at each layer.

Administrative processes may bypass RLS

This is a bit of a technical topic.
In business applications, there are processes that run on the system side, separate from those performed directly by users.

For example,

  • Bulk administrative operations
  • Batch processing
  • Integration with external systems
  • Data migration
  • Administrative API

and so on.
In some cases, this kind of processing handles data without being restricted by RLS.
Supabase provides privileges for server-side administrative operations, but these must never be exposed on the browser side.

ユーザーからの操作
↓
RLSを通す

管理・システム処理
↓
サーバー側で必要な権限を使う

as shown here.
If everything runs with administrative privileges, the purpose of setting up RLS is effectively lost.

In business applications, start by creating a permissions table

Before writing RLS, first organize the business permissions.
For example, an approval app might look like this.

                自分   拠点   全体   承認
一般ユーザー      ○     ×      ×      ×
上長              ○     ○      ×      ○
本部管理者        ○     ○      ○      ○

In practice, it is more granular,

  • View
  • Create
  • Update
  • Delete
  • Approve
  • Output

and so on.
Once you've decided on this,

どのテーブルに
どんな情報を持たせるか
↓
どのユーザーが
どの行にアクセスできるか
↓
RLSをどう設定するか

consider the following.
Before configuring RLS, organize the permissions your business needs.
RLS is simply a mechanism for enforcing those business-defined permissions in the database.

Designing permissions early is easier

When building a business application, it's common to hear, "Let everyone view everything for now and add permissions later."
That may be fine for a small prototype, but for production use, it's easier to design permissions early on.

later,

  • Split by company
  • Split by location
  • Add administrator
  • Add agency
  • Adding external staff

At that point, you may need to change the data structure itself.
The same applies when using RLS with Supabase.

Deciding in advance who shares which data, and at what level

will make the design considerably cleaner.

Summary

Using Supabase RLS allows the database itself to handle the fine-grained data access controls that business applications often require.

For example,

  • users can only see their own data
  • Show only data from the same company
  • Show only data from the same office
  • Administrators can view all records
  • Only specific people can make updates

These are examples of such controls.

Supabase Auth verifies "who the user is," while RLS controls "which data that user is allowed to access."

Together, these tools let you implement the access control that business applications require.
However, what matters most is not writing RLS itself.
First,

clarifying who can do what, and how far their access extends

in terms of business permissions.
Once that is settled, Supabase Auth, application-side permission control, and the role of RLS naturally follow.

Next time, we will cover why we combine Cloudflare Workers with Supabase.

About the author

Usually fairly laid-back, but when something catches my interest, I dig in until I fully understand it. Through plenty of trial and error, I quietly keep working on development day after day.

Matsun

Frontend Engineer

View this staff member's articles

Reliable team structure and responsive support are our strengths

Liberogic's experienced staff actively drive projects forward, which has earned high praise from our clients.
We make sure to properly assign project managers and directors to keep the entire project running smoothly. Our style of allocating resources to the right people, rather than adding full-time commitment unnecessarily, helps prevent cost increases. We are also known for our speed, from understanding the scope of work to preparing and submitting estimates.

Note: Please be advised that we do not actively engage in SES-style on-site staffing assignments.

We support nearly all major project management and chat tools, including Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom, and Webex.

Have a web challenge? Let us know.

Case Studies