🐖

FlutterのFirebaseログインでSupabaseのRow Level Securityを有効にする

2022/03/15に公開

tapecojapn株式会社のstdと申します。

Firebase便利でよくお世話になっているのですが、Cloud Firestoreでアプリの条件を満たすのが苦労する場合があります。
そんなときにFirebase Alternativeと謳われているSupabaseを使うとデータベースがPostgreSQLのため簡単に解決できたりします。
ただ、SupabaseのデータベースのRow Level Securityを使おうとするとSupabaseでログインしている必要があります。
そのため、Firebaseから完全に移行しなければならなく、基本はCloud Firestoreを使って一部分だけPostgreSQLを使いたいというパターンを満たすことができません。

こんな状況を解決する方法をまとめました。
同じような課題を抱いている方の助けとなれば幸いです。

https://firebase.google.com/?hl=ja
https://supabase.com/

がんばって設定する

ログイン状態をサブスクする

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