FlutterのFirebaseログインでSupabaseのRow Level Securityを有効にする
tapecojapn株式会社のstdと申します。
Firebase便利でよくお世話になっているのですが、Cloud Firestoreでアプリの条件を満たすのが苦労する場合があります。
そんなときにFirebase Alternativeと謳われているSupabaseを使うとデータベースがPostgreSQLのため簡単に解決できたりします。
ただ、SupabaseのデータベースのRow Level Securityを使おうとするとSupabaseでログインしている必要があります。
そのため、Firebaseから完全に移行しなければならなく、基本はCloud Firestoreを使って一部分だけPostgreSQLを使いたいというパターンを満たすことができません。
こんな状況を解決する方法をまとめました。
同じような課題を抱いている方の助けとなれば幸いです。
がんばって設定する
ログイン状態をサブスクする
void authStateChanges() {
FirebaseAuth.instance.authStateChanges().listen((User? user) async {
if (user == null) {
print('User is currently signed out!');
//ログアウト処理
} else {
print('User is signed in!');
//ログイン処理
}
});
}
Firebaseでログインする
今回ログイン方法は匿名にしていますが任意のログイン方法でOKです。
Future<void> signInAnonymously() async {
try {
await FirebaseAuth.instance.signInAnonymously();
} on Exception catch (onError) {
print(onError);
}
}
ここまでは通常のFirebaseのログインと同じです。
ここからFirebase AuthをSupabaseで使えるようにします。
Supabase管理画面の Settings/APIからJWT Secretをコピーする
SupabaseにFirebase Authをセットする
pubspec.yamlにdart_jsonwebtokenを入れPub getします。
dart_jsonwebtokenを使ってJWTを作成し、supabaseにセットします。
void setAuth(String uid) {
const secret = ''; //todo: JWT Secretをここに入れる。
try {
final jwt = JWT(
{
'uid': uid,
'role': 'authenticated',
},
);
final token = jwt.sign(
SecretKey(secret),
expiresIn: const Duration(hours: 1),
);
Supabase.instance.client.auth.setAuth(token);
} on Exception catch (onError) {
print(onError);
}
}
最初に作成したログイン状態をサブスクを書き換える
void authStateChanges() {
FirebaseAuth.instance.authStateChanges().listen((User? user) async {
if (user == null) {
print('User is currently signed out!');
//ログアウト処理
} else {
print('User is signed in!');
setAuth(user.uid);
}
});
}
これでSupabaseにFirebaseのログインIDを渡すことができましたのでSupabaseのRow Level Securityで利用できるようにします。
SupabaseのSQL Editerで関数を登録する
create or replace function request_uid() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'uid', '')::text;
$$ language sql stable;
create or replace function request_role() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'role', '')::text;
$$ language sql stable;
今回uidとroleのみでJWTを作成していますので関数は2つです。
もし他のプロパティも使用する場合関数を増やしてjson->>'uid'のuidを変更して使ってみてください。
簡単な使い方
Supabaseでは通常Authの情報をauth.uid()、auth.role()などで取得してRow Level Securityをかけたりするのですが、先ほど登録した関数を用いて下記のように書き換えます。
auth.uid() → request_uid()
auth.role() → request_role()
これで準備が整いました。
実際に使ってみる
サンプルテーブル作成
SupabaseのSQL Editerで下記を実行してmessagesテーブルを作成、Row Level Securityを有効にする。
create table if not exists public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, uid text not null
, title text not null
, created_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL
);
alter table messages enable row level security;
create policy "Enable read access for authenticated users only." on messages for select using (request_role() = 'authenticated');
create policy "Enable insert access for users based on their user ID." on messages for insert with check (request_uid() = uid);
create policy "Enable update access for users based on their user ID." on messages for update using (request_uid() = uid);
Flutterからmessagesテーブルに登録する
Future<void> insert(String title) async {
final user = FirebaseAuth.instance.currentUser;
final data = <String, dynamic>{
'uid': user!.uid,
'title': title,
};
final response = await Supabase.instance.client.from('messages').insert(data).execute();
if (response.error != null) {
print(response.error!.message);
} else {
print('success');
}
}
messagesテーブルのデータを取得する
Future<void> fetch() async {
final response = await Supabase.instance.client.from('messages').select().execute();
if (response.error != null) {
print(response.error!.message);
}
if (response.data != null) {
print(response.data);
}
}
コンソールに先ほど登録したデータが表示されれば成功です。
お疲れ様でした。
終わりに
そこまで複雑なことをしなくてもFirebaseとSupabaseを連携させることができました。
これにより、より柔軟にFirebaseとSupabaseを使えるようになると思います。
長文でしたがお読みいただきありがとうございました。
お仕事のご依頼はこちらまで。
info@tapecojapan.com
Discussion