SupabaseのローカルエミュレーターでOAuthプロバイダーによるログインを行う手順

2022/09/17に公開約5,300字

公式ドキュメントがなかったので備忘録です。

1. OAuthクライアントIDを作成する

各種サービスでOAuthクライアントIDを作成します。

その際、リダイレクトURLを http://localhost:54321/auth/v1/callback とします。

自分がググった限り特にドキュメントなどで言及されておらず、実際のHTTPリクエストの内容を見て知りました。

Twitterの例

https://developer.twitter.com/en/portal/apps/{アプリID}/auth-settings

Facebookの例

※Facebookの場合はアプリが開発モードならリダイレクトURLは設定不要。

https://developers.facebook.com/apps/{アプリID}/fb-login/settings/

Googleの例

https://console.cloud.google.com/apis/credentials/oauthclient/{クライアントID}?project={プロジェクトID}

2. 実装

Next.jsプロジェクトで @supabase/auth-helpers を使用している場合の例 で説明します。

まずは、例えば Twitterでログイン のようなボタンをクリックした際に以下のコードを実行します。

import {supabaseClient} from '@supabase/auth-helpers-nextjs'

supabaseClient.auth.signIn(
  {provider: 'twitter'}, // 例えばTwitterの場合
  {redirectTo: `${window.location.origin}/auth/callback`}, // 例えばこんなURL
)

そして、認証完了後のリダイレクト先として指定した /auth/callback ページを実装します。

このページに、アクセストークンなどがURLのフラグメントに付加された状態でリダイレクトされてきて、@supabase/auth-helpers<UserProvider> によってそれがパースされてユーザーがログインした状態になるので、 useUser() によってログインしたユーザーの情報を取得できます。

// pages/auth/callback.tsx

// 例えばこんな感じの処理
import {useUser} from '@supabase/auth-helpers-react'
import {useRouter} from 'next/router'
import {useEffect} from 'react'

const Callback: NextPage = () => {
  const {user} = useUser()
  const router = useRouter()

  useEffect(() => {
    if (user) {
      // 「ソーシャルアカウントでログインしました」などのtoastを表示したり
      // auth.usersテーブルとは別にpublic.profilesテーブルなどがある場合はユーザーデータを取得して状態を保存したり
      // をここで行う
      
      router.replace('/')
    }
  }, [user, router])

  return <></>
}

export default Callback

supabaseClient.auth.signIn() の第2引数で redirectTo を指定して専用のコールバック処理ページを設けるのは必須の対応ではないですが、こうしないと、ログイン後はトップページに #access_token=xxxxx&... のような長〜いフラグメントが付加されたURLで戻ってくるので体験が良くないのと、専用のページに戻ってくるようにしておけば「ソーシャルアカウントでログインしました」といったtoastを表示するなどログイン手法ごとの処理を気軽に追加できるのでおすすめです。

ちなみに

ちなみに、Supabase AuthではOAuthプロバイダーからユーザーのメールアドレスを受け取り、メールアドレスによってユーザーの一意性を決定します。

なので、例えばユーザーが

  1. まずメールアドレスとパスワードでサインアップ
  2. 1. で登録したメールアドレスと同じメールアドレスが登録されているSNSアカウントでOAuthログイン

などの手順を踏んだ場合、2. では新しいユーザーは作成されず、1. で作られたユーザーがログインしたものと見なされます。

3. supabase/config.toml を設定

最後に、ローカルのSupabaseエミュレーターの設定ファイルである supabase/config.toml に以下のような設定を追記します。

OAuthクライアントID・シークレットは秘匿情報なので .env.local などに記載して .gitignore します。

[auth.external.twitter]
enabled = true
client_id = "env(DEV_TWITTER_CLIENT_ID)"
secret = "env(DEV_TWITTER_SECRET)"

[auth.external.facebook]
enabled = true
client_id = "env(DEV_FACEBOOK_CLIENT_ID)"
secret = "env(DEV_FACEBOOK_SECRET)"

[auth.external.google]
enabled = true
client_id = "env(DEV_GOOGLE_CLIENT_ID)"
secret = "env(DEV_GOOGLE_SECRET)"
# .env.local
DEV_TWITTER_CLIENT_ID={TwitterのクライアントID}
DEV_TWITTER_SECRET={Twitterのクライアントシークレット}
DEV_FACEBOOK_CLIENT_ID={FacebookのクライアントID}
DEV_FACEBOOK_SECRET={Facebookのクライアントシークレット}
DEV_GOOGLE_CLIENT_ID={GoogleのクライアントID}
DEV_GOOGLE_SECRET={Googleのクライアントシークレット}

参考:https://github.com/supabase/cli/issues/210

その上で、例えば dotenv-cli を導入して以下のようなnpm scriptsを定義しておきます。

{
  "scripts": {
    "supabase": "node_modules/supabase/bin/supabase",
    "supabase:start": "dotenv -c local -- bash -c 'yarn supabase start'",
    "supabase:stop": "dotenv -c local -- bash -c 'yarn supabase stop'"
  },
  "devDependencies": {
    "supabase": "^1.4.5"
  }
}

これで、

$ yarn supabase:start
$ yarn supabase:stop

で、OAuthプロバイダーからのリダイレクトを正しく捌ける状態のエミュレーターを起動・終了できます。

4. おまけ:CIでマイグレーションをしている場合はCI環境にも環境変数を適当に設定

CIで supabase db push を行っている場合は、config.tomlenv() を書いてしまったために、設定ファイルの読み込みエラー回避のためにCIのプロセスにも環境変数を適当な内容で与えておく必要があります。

例えばGitHub Actionsの場合なら、以下のような感じで設定しておくことになるかと思います。

  name: CI
  
  on: push
  
  jobs:
    test:
      # ...
  
    deploy:
      if: ${{ github.ref == 'refs/heads/main' }}
      needs: test
      # ...
  
    deploy-supabase:
      if: ${{ github.ref == 'refs/heads/main' }}
      needs: deploy
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v2
        - uses: actions/setup-node@v3.0.0
          with:
            node-version: 16
            check-latest: true
        - run: yarn
        - run: |
            yarn yarn supabase link --project-ref $SUPABASE_PROJECT_REF
            yarn yarn supabase db push
          shell: bash
          env:
            SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
            SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
            SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }}
+           DEV_TWITTER_CLIENT_ID: nop
+           DEV_TWITTER_SECRET: nop
+           DEV_FACEBOOK_CLIENT_ID: nop
+           DEV_FACEBOOK_SECRET: nop
+           DEV_GOOGLE_CLIENT_ID: nop
+           DEV_GOOGLE_SECRET: nop

参考:https://supabase.com/docs/guides/cli/cicd-workflow#deploying-a-migration

GitHubで編集を提案

Discussion

ログインするとコメントできます