😊

Nuxt + SupabaseでGoogleログインをGoogle pre-builtで実装する

2024/08/13に公開

基本はドキュメント通りに設定・実装すれば問題ないが、地味に悩んだところがあったのでメモ。

Google クライアントライブラリのロード

ドキュメントでは script タグからロードしろとあるが、Nuxt で実装する場合、基本的に script タグを直接実装することはない。
https://supabase.com/docs/guides/auth/social-login/auth-google?queryGroups=environment&environment=server&queryGroups=platform&platform=web&queryGroups=framework&framework=nextjs#google-pre-built

対応方法

これは useHead を使用することで解決できる。

https://nuxt.com/docs/api/composables/use-head

<script setup lang="ts">
useHead({
  script: [
    {
      src: "https://accounts.google.com/gsi/client",
      async: true,
    },
  ],
});
</script>

Google ログイン成功時のコールバック関数の定義

Google の HTML コードジェネレーターでログインボタンを生成すると以下のような HTML を得られる。

<div
  id="g_id_onload"
  data-client_id="クライアントID"
  data-context="signin"
  data-ux_mode="popup"
  data-callback="handleSignInWithGoogle"
  data-auto_prompt="false"
  data-nonce=""
  data-auto_select="true"
  data-itp_support="true"
  data-use_fedcm_for_prompt="true"
></div>

ここで、data-callback に指定するコールバック関数handleSignInWithGoogleはグローバルにアクセス可能である必要がある。

https://supabase.com/docs/guides/auth/social-login/auth-google?queryGroups=environment&environment=server&queryGroups=platform&platform=web&queryGroups=framework&framework=nextjs#google-pre-built:~:text=Create a handleSignInWithGoogle function that takes the CredentialResponse and passes the included token to Supabase. The function needs to be available in the global scope for Google's code to find it.

Create a handleSignInWithGoogle function that takes the CredentialResponse and passes the included token to Supabase. The function needs to be available in the global scope for Google's code to find it.

これは Nuxt や Vue の文脈とは関係なしにglobal scopeである必要があるため、plugin として実装して$でアクセスするとかそういう話ではない。ブラウザのデベロッパーツールで開いたコンソールからアクセスできなきゃいけないようなイメージ。

対応

window オブジェクトに関数をもたせる方法で対応可能。

<script setup lang="ts">
const supabase = useSupabaseClient();

const handleSignInWithGoogle = async (response: { credential: string }) => {
  const { data, error } = await supabase.auth.signInWithIdToken({
    provider: "google",
    token: response.credential,
  });
  // ...
};

onMounted(() => {
  (window as any).handleSignInWithGoogle = handleSignInWithGoogle;
});
onUnmounted(() => {
  delete (window as any).handleSignInWithGoogle;
});
</script>

最適な方法かは不明だが、ログインボタン用のコンポーネント内で関数を定義して Unmount 時に削除してあげれば、グローバルスコープではあるけど実際のスコープを小さくできるのでよさげな気がする。

Discussion