挫折しました。。。
セキュアなアプリを目指して、Supabaseの RLS (Row Level Security) を活用し、ログイン状態に応じてデータベースの参照・操作を制限しようとしました。
Supabase RLSは、Supabase Auth を使えば簡単に設定できるようですが、今回のアプリではすでに Firebase Auth を導入済み。
そこで、Firebaseの認証を維持したまま、Supabase RLS を適用する方法を模索しました。
環境
- フレームワーク: Nuxt.js (Vue)
- 認証: Firebase Authentication
- データベース: Supabase
開発の流れ
まずはSupabase RLSを使用して一番簡単であろう"ログイン中のユーザーはSELECTでデータを参照できる"を目指します。
- rls_testというテーブルにユーザーがログインしていたらSELECTできるというポリシーを作成
- supabaseの認証にfirebaseを追加
- firebaseに対してcloud functionでrole: "authenticated"を設定
- SupabaseClient初期化時にfirebase tokenを設定する
- テーブルをSELECTしてみる
結果、SupabaseClient初期化時にfirebase tokenを渡せず失敗
1,2,3はうまく設定できました。
公式ドキュメントを参考にしつつ、実装のイメージが全くついていなかったFirebaseのcloud functionでのroleの設定も確認できました。
4も実装し5で対象テーブルのデータを取ろうとしましたが、取れていない、、、。
RLSポリシーを無効化するとテーブルのデータが取得できるので問題は4にありそうです。
残念ながら、今回達成できたのはここまででした。
Firebaseのcloud functionでrole: "authenticated"を設定後、4のSupabaseClientをfirebase token付きで初期化しようとしましたが、ログで確認したところ、初期化したSupabaseClientの中にaccessTokenがありませんでした。
実際のコード
const supabase = createClient<Database>(
runtimeConfig.public.supabaseUrl,
runtimeConfig.public.supabaseKey,
{
accessToken: async () => {
return await user.value?.getIdToken(true) ?? null
},
}
)
console.log(supabase)
console.logしている箇所の出力結果
SupabaseClient {supabaseUrl: 'https://xxxx.supabase.co', supabaseKey: 'xxx', realtimeUrl: ...
accessToken: async () => {…}
length: 0
name: "accessToken"
arguments: (...)
caller: (...)
accessTokenのlengthが0なので設定ができていないようです。
argumentsとcallerにはこのようなエラー分が出力されていました。
arguments
:
[Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at accessToken.invokeGetter (<anonymous>:3:28)]
上記のことから、accessTokenが設定できていなそうだと予想しております。
ちなみに今回設定したSupabase RLSのポリシーは以下です。
using trueとすることで、RLS を有効にしつつ、とりあえず「誰でも参照できる」状態にしてテストしています。
alter policy "Enable read access for all users"
on "public"."rls_test"
to public
using (
true
);
(※RLSを無効にするとテーブルは参照できることを確認済み)
エラー解決に向けて試したこと
-
Supabaseのバージョンを確認・更新
@supabase/supabase-js のバージョンが古い可能性があるため、最新版へ更新してみる
npm update @supabase/supabase-js
→ 変化なし。エラーは依然発生。 -
SupabaseClientのaccessToken オプションの指定方法を変更
createClientのaccessTokenオプションが非同期関数 (async () => {})のままで期待通りに動作していない可能性を考え、明示的にawaitで取得したトークンを渡すように変更しました。
const token = await user.value?.getIdToken(true);
const supabase = createClient<Database>(
runtimeConfig.public.supabaseUrl,
runtimeConfig.public.supabaseKey,
{ accessToken: token }
);
→ 依然として accessToken が設定されていない。
- Supabase の auth.exchangeCodeForSession を試す
AIに相談すると違う方法でaccessTokenを付与する方法も提案されました。
auth.signInWithIdToken を使用することで、FirebaseのtokenをSupabaseに渡す方法もあるみたいです。
const token = await user.value?.getIdToken(true);
const { error } = await supabase.auth.signInWithIdToken(token);
if (error) console.error("Sign-in error:", error);
他にもsetTimeoutで数秒待ってみたり、エラーでググりまくりましたが有効な解決手段が見つからず断念。
今後どうしていくか
今回はNuxt.jsのアプリの中で全てのコードを書いていました。
Firebaseのcloud functionを使用し、完全に別サーバーで試してみようかとも思っています。
最後に
今回の挑戦は、悔しい結果に終わりました。
公式サイトもあり仕組み的にもアンチパターンでないため悔しいですね。
調査スキルを上げていきたい次第です。
公式ドキュメントの読み込みや、Cloud Function の設定方法を学べたことは大きな収穫でした。
最後まで読んでいただきありがとうございました。