🗂

Next.jsでsupabaseのAnonymous Sign-Insを使う

2024/05/10に公開

Next.jsでSupabaseのAnonymous Sign-Insを試してみたので実装についてまとめます。

Anonymous Sign-Insについて

Anonymous Sign-Insを有効にすることでユーザーにメールアドレスやパスワード入力やOAuthプロバイダーなどの個人を特定できる情報を要求せずに認証機能を構築することができます。

今回は使い捨てアカウントでメッセージを投稿する機能を実装したかったので、supabaseのAnonymous Sign-Insを使いました。

ドキュメントに記載もありますが、Anonymous Sign-Insは下記のようなことに使うことができます。

  • チェックアウト前のショッピングカートなどのEコマース・アプリケーション
  • 個人情報を収集しないフル機能のデモ
  • 一時的または使い捨てアカウント

https://supabase.com/docs/guides/auth/auth-anonymous

やらないこと

  • Supabaseプロジェクトのセットアップ、Next.jsアプリの作成は取り扱いません。
    詳細は公式ドキュメントをご確認ください。

https://supabase.com/docs/guides/getting-started/quickstarts/nextjs

  • 匿名ユーザーにメールアドレス/電話番号やOAuth IDを紐づけることで永続的なユーザーに変換することができますが、今回はそこまで行いません。
    詳細は公式ドキュメントをご確認ください。

https://supabase.com/docs/guides/auth/auth-anonymous#convert-an-anonymous-user-to-a-permanent-user

実装

signInAnonymously()を呼び出すための関数を作成

actions.ts
'use server'

export async function anonymousSignIn() {
  const supabase = createClient();
  const { error } = await supabase.auth.signInAnonymously();
  if (error) {
    // エラー時の処理
  }
  revalidatePath('/', 'layout');
  redirect('/');
}

サインインページでactionsを呼び出す

anonymousSignInをformのactionに渡します。

signin/page.tsx
import { anonymousSignIn } from '~/actions';

export default async function SignIn() {
  return (
    <form action={anonymousSignIn}>
      <button type='submit'>SignIn</button>
    </form>
  );
}

ボタンを押すことで関数が実行されていることが確認できました。

※supabaseのEnable Captcha protectionを有効化しているためこの時点では失敗しています。

下記ドキュメントに記載されていますが、匿名サインインの悪用を防ぐためにEnable Captcha protectionを有効にすることが強く推奨されています。
Abuse prevention and rate limits

Cloudflare Turnstileのセットアップ

  1. Cloudflare Webサイトにアクセスしてサインアップ後、新しいサイトを作成してSite Key、Secret Keyを取得します。

  2. ダッシュボードのProject Setting/Authenticationに移動します。
    Enable Captcha protectionを有効化、Captcha ProviderとしてCloudflare Turnstileを選択して、Captcha secretに先ほど取得したSecret Keyを設定します。

  3. Captchaコンポーネントを追加します
    以下コマンドを実行してTurnstile Reactコンポーネントをインストールします。

npm install @marsidev/react-turnstile

TurnstileコンポーネントにsiteKey、コールバック関数を渡します。

<Turnstile
  siteKey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
  onSuccess={(token) => {
    setCaptchaToken(token);
  }}
/>

SignInを押すことでsupabaseにユーザーが作成されることが確認できました。


hCaptcha、Cloudflare Turnstileの設定については以下ドキュメントに詳細がありますのでこちらをご確認ください。
https://supabase.com/docs/guides/auth/auth-captcha?queryGroups=captcha-method&captcha-method=turnstile-1

この時点でのactions.tspage.tsxの実装は以下の通りです。

actions.ts
export async function anonymousSignInAction(captchaToken: string, _: FormData) {
  const supabase = createClient();
  const { error } = await supabase.auth.signInAnonymously({
    options: {
      captchaToken: captchaToken,
    },
  });
  if (error) {
    // エラー時の処理
  }
  revalidatePath('/', 'layout');
  redirect('/signin');
}
signin/page.tsx
'use client';
import { Turnstile } from '@marsidev/react-turnstile';
import { useState } from 'react';

import { anonymousSignInAction } from '~/actions';
import { env } from '~/env.mjs';

export default function SignIn() {
  const [captchaToken, setCaptchaToken] = useState('');

  const anonymousSignIn = anonymousSignInAction.bind(null, captchaToken);

  return (
    <div className='flex flex-col items-center justify-center'>
      <Turnstile
        siteKey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
        onSuccess={(token) => {
          setCaptchaToken(token);
        }}
      />
      <form action={anonymousSignIn}>
        <button type='submit'>SignIn</button>
      </form>
    </div>
  );
}

captchaTokenを保持するためにuseStateを使用しているため、いったんpage.tsxに対して'use client'を付けています。
また、Server ActionsにcaptchaTokenを渡すためにbindを使っています。

bind以外の方法

bindを使わずにcaptchaTokenを渡す方法として非表示の入力フィールドを使うこともできます。

<form action={anonymousSignIn}>
   <input className='hidden' name='captchaToken' value={captchaToken} />
  <button type='submit'>SignIn</button>
</form>

formData.get('captchaToken')とすることでServer ActionsでcaptchaTokenを使うことができます。

actions.ts
const { error } = await supabase.auth.signInAnonymously({
  options: {
    captchaToken: formData.get('captchaToken'),
  },
});

まとめ

signInAnonymouslyを使ってユーザー作成できるところまでざっくりとまとめました。
今回の方法ではcaptchaTokenの保持にuseStateを使っていますが、useActionStateconformを使った実装でも動作することが確認できました。
captchaTokenが取得される前にボタンが押されたときにクライアント側でバリデーションを行ったり、サインイン処理が完了するまでisPendingでローディングを表示するといったことができるため、useActionStateとconformを使った実装を行うのが良さそうでした。

参考

https://tigerabrodi.blog/nuances-of-server-actions-in-nextjs

Discussion