Supabase の RLS(Row Level Security)でカスタムトークン(JWT)を使用する

Supabase の RLS(Row Level Security)でカスタムトークン(JWT)を使用する

November 27, 2022

Supabase(= PostgreSQL)では RLS(Row Level Security)機能を用いてポリシーを作成することでアクセス制御が可能です。

ユーザの認証を Supabase Auth で行っている場合、ポリシーの中で Auth の情報にアクセスすることで、ユーザに応じた細かなアクセス制御が可能となります。

例えば auth.uid() とすることでアクセスしたユーザの uuid を取得することができるため、次のようにポリシーを記載することができます。

/*
 自分が著者である記事のみ取得可能
 */
CREATE POLICY "Articles select policy" ON articles FOR
SELECT
    USING ("author_id" = auth.uid());

外部の認証サービスを用いている場合 #

データベースは Supabase を使用するものの、認証情報の管理には Supabase Auth ではない別のサービスを使いたい場合もあります。

例えば Firebase Authentication、Amazon Cognito、Auth0 などがあるでしょう。

Supabase Auth を用いない場合でも、これから記載する方法で最初の例と同じようにポリシー内で認証情報を参照することが可能となります。

1. サーバサイドで JWT を生成する #

言語は JavaScript で記載しています。コードは簡略化された例です。

import jwt from 'jsonwebtoken';

const supabaseJwtSecret = 'super-secret-jwt-token-with-at-least-32-characters-long';
const token = { userId: '********-****-****-****-************' };
const signedToken = jwt.sign(token, supabaseJwtSecret);

res.send(signedToken);

サーバサイドで上記処理を行い、フロントエンドへ JWT を渡します。

暗号鍵には Supabase の JWT Secret を使用します。これは秘密鍵のためサーバサイドでのみ使用してください。

JWT Secret は Supabase ダッシュボードの Settings > API > JWT Settings で確認できます。(上記の例ではローカル環境の Supabase の JWT Secret を記載しています。)

2. フロントエンドで JWT を用いて Supabase クライアントを生成する #

import { createClient } from '@supabase/supabase-js';

const supabaseApiUrl = 'http://localhost:54321';
const supabaseAnonRoleKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.********';
const token = '****JWT from server side ****';

supabase = createClient(supabaseApiUrl, supabaseAnonRoleKey, {
  global: {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  },
});

先ほど生成した JWT を受け取って Supabase クライアントを生成します。

このクライアントで DB にアクセスします。

const articles = await supabase.from('articles').select();

トークンを付加した状態で Supabase の DB にアクセスするため、RLS のポリシーで JWT を参照することができます。

3. RLS のポリシーで JWT を参照する #

/*
 JWT に userId がセットされていれば記事が参照可能
 */
CREATE POLICY "Articles select policy" ON articles FOR
SELECT
    USING (((current_setting('request.jwt.claims'::text, true))::json->> 'userId'::text) IS NOT NULL)

このようにしてクライアントに付加されている JWT を参照できます。

上記は userId に何かしらの値が入っていれば記事の参照を可能とするというポリシーです。

/*
 自分が著者である記事のみ取得可能
 */
CREATE POLICY "Articles select policy" ON articles FOR
SELECT
    USING ("author_id"::text = (((current_setting('request.jwt.claims'::text, true))::json ->> 'userId'::text)));

こちらは本記事冒頭の例と同じことを実現しているポリシーです。自分が著者である記事のみ取得可能としています。

参考 #

補足

上記記事内で登場する setAuth() のメソッドは Supabase のライブラリのアップデートによって変更されています。

SQL 関数を作成して便利に使う #

ここまでで機能の実現はできました。しかし複数のポリシーで JWT から userId を取得する場合、毎回 (((current_setting('request.jwt.claims'::text, true))::json ->> ......... を記載するのは冗長です。

auth.uid() と同じように利用するため SQL 関数を定義します。

/*
 JWT から userId を取得する
 */
CREATE OR REPLACE FUNCTION auth.user_id() RETURNS text AS $$
    SELECT ((current_setting('request.jwt.claims'::text, true))::json ->> 'userId')::text;
$$ language SQL stable;

/*
 自分が著者である記事のみ取得可能
 */
CREATE POLICY "Articles select policy" ON articles FOR
SELECT
    USING ("author_id"::text = auth.user_id());

なお “author_id” が UUID 型ならば次のようにできます。

/*
 JWT から userId を取得する
 */
CREATE OR REPLACE FUNCTION auth.user_id() RETURNS uuid AS $$
    SELECT ((current_setting('request.jwt.claims'::text, true))::json ->> 'userId')::uuid;
$$ language SQL stable;

/*
 自分が著者である記事のみ取得可能
 */
CREATE POLICY "Articles select policy" ON articles FOR
SELECT
    USING ("author_id" = auth.user_id());

これで auth.uid() と同じ使い心地が実現できます。

参考 #