Open22

supabase勉強

shinnshinn

supabaseを使うことになったので調査します。
dbは個人ではsqliteを触った程度なので、間違っている可能性が全然あります。
正確さはないので間違いがあったら教えて欲しです。

shinnshinn

authスキーマのuserテーブルは変えられない
Triggerで情報を転写したほうがいいのだろうか?
とりあえずauth.usersにリレーションを貼ったテーブル

-- ENUM型の作成
CREATE TYPE authority_type AS ENUM ('general', 'admin');

-- テーブルの作成
CREATE TABLE authority (
     id UUID NOT NULL PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
    authority authority_type DEFAULT 'general'
);
shinnshinn

セキュリティのためにRLSというものがある。

  • supabaseライブラリがbaasでフロントエンドでも操作できる。
  • だが、フロントエンドで使えるようにすると自由にDBを操作される可能性がある。
  • そこでRLSの出番。
  • ユーザがログインするとセッションが確立される
  • クライアントから送られてくる指示に対してauth.uid()=user_idのようにユーザを制限できるようになるらしい

とりあえず、ユーザ制限のポリシーを追加してみる。

CREATE POLICY "select for users based on user_id" ON "public"."authority"
AS PERMISSIVE
FOR SELECT
TO public
USING (auth.uid() = id)
shinnshinn

auth.usersにレコードが追加されたときに転記するTriggerを作成する。

create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.authority  (id)
  values (new.id);
  return new;
end;
$$
 language plpgsql security definer;

create trigger copy_new_user
after insert on auth.users for each row
execute procedure public.handle_new_user();

参照:https://stackoverflow.com/questions/77751173/getting-auth-users-table-data-in-supabase

shinnshinn

勝手にアカウントを作られると困るので規制する。

  • cloudの場合
    setting->Authentication->Allow new users to sign upをoff
  • supabase cli
    config.tomlから該当の設定事項をFalseにする
shinnshinn

supabaseライブラリ自体はBaaSなので、バックエンドでのsupabaseライブラリ自体では持続的な接続はできないということなのかもしれない。

shinnshinn

多分、いくつか対処法はあるが、バックエンドで認証を行うためにはJWTを使うのがいいのだろう。
supabaseにおいて、アクセストークンがJWTに対応している。
ただ、jwtにメールアドレスなどのPII(個人識別情報、初めて知った)が含まれていることが懸念点。
これについてはgitでも問題として挙がっているがあまり議論されていない。
何故だろう?

バックエンド:https://depshub.com/blog/using-supabase-auth-as-a-service-with-a-custom-backend/
jwtについて:https://qiita.com/asagohan2301/items/cef8bcb969fef9064a5c
アクセストークンPII:https://github.com/orgs/supabase/discussions/22276

shinnshinn

今更だが、supbase cliをというのがある。
とりあえずwsl環境で動かしている。(参照したサイトは忘れました)
クラウドのsupabaseとは勝手が違うので少し戸惑うことはあるが、基本的に設定できるぽい

shinnshinn

supabase cliにはinbucketというサービスが使用できる。これはメールの受信処理を疑似的にできる。
次のアドレスからアクセスできる。
http://localhost:54324/

shinnshinn

こんな、方針かな???

  1. クライアントでsupabase jsで認証を行う。
  2. cookieを設定して、apiサーバーでトークンを検証する。
shinnshinn

ブラウザがアクティブならsupabase jsが勝手にリフレッシュトークンを使って、アクセストークンを更新するっぽい。

shinnshinn

サインインの方法はパスワード認証をするか、OTP経由で認証を行うか。(ほかにもいろいろな認証を行える)
OTPはデフォルトでは、supabase側で検証を行って、アクセストークンをハッシュパラメータにのせて、リダイレクトしてくるみたい。
リダイレクト先はoptのパラメータで指定できる。
https://supabase.com/docs/reference/javascript/auth-signinwithotp
ワンタイムパスワードによる認証も行える。
eメールのテンプレートを変えれば、いろいろ変更できそう。

shinnshinn

マジックリンク経由のリダイレクトのハッシュパラメータ受け取ったら、勝手にログインしてくれると思ったらしてくれなかった。
OAuthは自動で識別してくれるらしいから勝手にログインしてくれると思ってた。
createClientでアクセスキーを指定してもローカルストレージにセッション情報は保存されなかった。
リフレッシュトークンの情報を取得できていないからだろうか?
https://supabase.com/docs/reference/javascript/initializing

shinnshinn

リフレッシュトークンは基本的に無期限だから、一度、ログインしたら、実質的に無期限にログイン状態を維持できるみたい。(多分、safariは7日間でローカルストレージが削除されるから別)

shinnshinn

python側で検証を行うとき、全然、検証できなったが、audienceをつけることで解消
https://github.com/orgs/supabase/discussions/20763
改善前

def decode_jwt(token: str, secret_key: str):
    """
    提供された秘密鍵を使用してJSON Webトークン(JWT)をデコードします。

    引数:
        token (str): デコードするJWT。
        secret_key (str): JWTをデコードするための秘密鍵。

    戻り値:
        tuple: ブール値とデコードされたトークンまたはエラーメッセージを含むタプル。
            - デコードが成功した場合、(True, decoded_token) を返します。
            - トークンの有効期限が切れている場合、(False, "トークンの有効期限が切れています") を返します。
            - トークンが無効な場合、(False, "無効なトークンです") を返します。
    """
    
    try:
        decoded = jwt.decode(token, secret_key, algorithms="HS256")
        return True, decoded
    except jwt.ExpiredSignatureError:
        return False, "トークンの有効期限が切れています"
    except jwt.InvalidTokenError:
        return False, "無効なトークンです"

改善後

def decode_jwt(token: str, secret_key: str):
    """
    提供された秘密鍵を使用してJSON Webトークン(JWT)をデコードします。

    引数:
        token (str): デコードするJWT。
        secret_key (str): JWTをデコードするための秘密鍵。

    戻り値:
        tuple: ブール値とデコードされたトークンまたはエラーメッセージを含むタプル。
            - デコードが成功した場合、(True, decoded_token) を返します。
            - トークンの有効期限が切れている場合、(False, "トークンの有効期限が切れています") を返します。
            - トークンが無効な場合、(False, "無効なトークンです") を返します。
    """
    
    try:
        decoded = jwt.decode(token, secret_key, algorithms="HS256", audience='authenticated')
        return True, decoded
    except jwt.ExpiredSignatureError:
        return False, "トークンの有効期限が切れています"
    except jwt.InvalidTokenError:
        return False, "無効なトークンです"
shinnshinn

redirectを変更するときはAuthentication>URL Configurationで設定を行った。

shinnshinn

awsのsesを使って、カスタムsmtp設定を行う。
一番わかりやすかったのが以下のページ
https://darrenhinde.com/articles/AWS-SES-Supabase
方法

  1. route53経由でsesの登録を行う。(route53以外の人は他のページ参照)
  2. 本番環境のリクエストを行う。
  3. smtp用のIAMユーザを作成する。(SESのフルアクセス)
  4. smtp設定からSMTP エンドポイントをコピーする。
  5. supabaseにsmtp設定に各種情報を書き込む。
shinnshinn

色々、実際に起動設定していく中で、custom claimの設定が重要だと感じた。
まずclaimとはアクセストークンのことで認証情報のことだ。
これは標準で決まっているものではあるが、Custom Access Token Auth Hookを通じて、情報の追加を行うことができる。

shinnshinn

合っているかわ分からないがcustom claimの良い点、悪い点をまとめる。
良い点

  • アクセストークン自体にロールなどを含めることができるため、プログラム的に扱いやすい。(アクセストークンを検証するだけでロールの管理者かどうかなどの情報が取得できるため)
  • データベースへの接続回数が減る。(例えば、管理者のみのページだとすると、ページごとのデータベースへの確認が必要なくなる)
  • データベースへの接続回数が減るため、レスポンスが若干上がる

悪い点

  • ロールの変更が起こった時、サーバが知ることができない。
  • サーバが知ることができないためアクセストークンのリフレッシュまで、管理者ページの閲覧ができてしまう。(supabaseではデフォルトでは1時間ぐらいでリフレッシュされる)

対策としては、アクセストークのリフレッシュまでの時間を短くすることぐらいでしょうか?
ただ、ユーザ自身がログアウト動作を行い、再ログインを行うと当たり前だがアクセストークンはリフレッシュされる。