はじめに
SupabaseのRLSをFirebase Authを使っているプロジェクトで有効にしたのでまとめます。
つまずきどころが多く大変だったので、誰かの参考になれば嬉しいです。
そもそもRLSとは
RLS(Row Level Security)は、PostgreSQLのセキュリティ機能です。
データベースの行レベルでアクセス制御を行う仕組みのことを指します。
テーブル全体へのアクセス制御ではなく、特定の行(レコード)に対してユーザーごとに異なるアクセス権限を設定できます。
Supabase Authを使っているアプリケーションなら簡単に設定ができるのですが、今回はFirebase Authでやってみます。
前提条件
- Firebase Authを使用していること
- Supabaseをデータベースとして使用していること
設定手順
基本的には公式ドキュメントに書いてあります。
本記事も公式ドキュメントに従って手順を踏んでいきます。
1. Supabase側でThird-Party Authを有効にする
Supabaseにログインし、Authentication -> Sign In /Providers -> Third-Party Authを有効にします。
Third-Party AuthenticationからFirebaseを選択し、ご自身のFirebaseのProject IDを入力します。
2. FirebaseユーザーにRoleを追加する
ユーザーのアカウントのカスタム属性を変更します。
ドキュメントに詳細は書いてありますが、ユーザーに対してauthenticated
を付与することでデータベースルールを適用できるようです。
新規追加されたユーザーにauthenticated
を割り当てる
functions/index.js
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
exports.setCustomUserClaims = functions.auth.user().onCreate(async (user) => {
try {
if (user.email) {
await admin.auth().setCustomUserClaims(user.uid, {
role: "authenticated"
});
}
} catch (error) {
console.error("Error setting custom claims:", error);
}
});
以下を実行し、functionsを有効にします。
firebase deploy --only functions
既存ユーザーにauthenticated
を割り当てるスクリプト
const { initializeApp } = require('firebase-admin/app');
const { getAuth } = require('firebase-admin/auth');
initializeApp();
async function setRoleCustomClaim() {
let nextPageToken = undefined;
do {
const listUsersResult = await getAuth().listUsers(1000, nextPageToken);
nextPageToken = listUsersResult.pageToken;
await Promise.all(listUsersResult.users.map(async (userRecord) => {
try {
await getAuth().setCustomUserClaims(userRecord.uid, {
role: 'authenticated'
});
console.log(`Role set for user: ${userRecord.uid}`);
} catch (error) {
console.error(`Failed to set role for user ${userRecord.uid}:`, error);
}
}));
} while (nextPageToken);
}
setRoleCustomClaim().then(() => process.exit(0));
これで全てのユーザーに対してデータベースルールが適用されます。
3. Supabaseクライアントを設定
アプリケーションでSupabaseクライアントを作成している箇所で、Firebaseのユーザーのtokenを渡すようにします。
import { createClient } from '@supabase/supabase-js'
import { getAuth } from 'firebase/auth'
const auth = getAuth()
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key',
{
accessToken: async () => {
const user = auth.currentUser
if (user) {
return await user.getIdToken(false) // 通常はfalseでキャッシュを利用
}
return null
}
}
)
4. SupabaseのRLSを有効にする
SupabaseのGUIからRLSを有効にします。
続いて、GUIのSQLエディターから対象のテーブルに対してRLSポリシーを作成します。
まずはSELECTできるようにします。
CREATE POLICY "Enable read access for own data"
ON "public"."users"
FOR SELECT
TO authenticated
USING (uid = (auth.jwt() ->> 'sub'));
そのテーブルのuid
と、現在テーブルにアクセスしているユーザーが一致しているかをチェックしています。
UUIDの型の不一致で詰まったのでうまくいかないときは以下を試してみてください。
-- uid列をtext型に変更することを推奨
ALTER TABLE "public"."users" ALTER COLUMN uid TYPE text;
-- または、キャストして比較
USING (uid::text = (auth.jwt() ->> 'sub'))
正しく設定ができていれば、uid
が一致しているユーザーのみの行が取得できるはずです。
最後に
初めての技術でつまずきどころが多く時間がかかってしまいましたが、なんとか実装できました。
終わってみると必要なことは全て公式ドキュメントに書いてあるのだと分かりますが、初めての概念があると理解が追いつかず苦戦しました。
特にFirebaseのRoleの設定とRLSの見慣れないポリシー設定のSQLはとっつきにくいので、同じように悩まれている方の参考になれば嬉しいです。