LoginSignup
30
24

More than 1 year has passed since last update.

話題のSupabaseでサクッと認証機能をつくってみた!

Last updated at Posted at 2023-01-10

はじめに

こんにちは!かほです♪
現在、本業では技術広報業務やエンジニア業務などに従事しています。
今回の記事では、Next.js×Supabaseを用いたアプリ開発における簡易的な認証機能の実装方法について説明します。

ここで言う認証機能は下記の4点です。

  • サインアップ
  • ログイン
  • ログアウト
  • パスワードリセット

Next.js×Supabase×vercelの連携方法から知りたい方は、下記の記事を参考にしてください。

Next.js×Supabaseで、認証機能を実装したい方々の一助になれば幸いです🙌

※今回は簡易的な認証周りの実装を一通り説明することをテーマにしています。
そのため、認証によるページへのアクセス利用の有無、パスワード等の二重登録や文字設定などの
より詳細な実装は省いておりますので、あらかじめご了承ください🙏

この記事の読者対象

・supabaseの認証周りについて知りたい方
・Next.js×Supabaseで認証機能を含むアプリを作ってみたい方
・Supabaseを今後使ってみたい方
・Next.jsを使って高速でアプリを作りたい方

開発環境

package.json
"dependencies": {
    "@supabase/supabase-js": "^2.2.1",
    "eslint": "8.28.0",
    "eslint-config-next": "13.0.6",
    "next": "13.0.6",
    "react": "18.2.0",
    "react-dom": "18.2.0",
  },
  "devDependencies": {
    "@types/node": "^18.11.13",
    "@types/react": "18.0.26"
  }

ユーザーの認証方法について

Supabaseのユーザー認証の方法には、4通りあります。
今回使用する方法は、Eメールとパスワードによる認証です。
下記のSupabase公式ドキュメントに、その他認証方法についても詳しく書いてあるため、ご興味のある方は、ご参照ください。

各コンポーネントの説明

作成した各コンポーネントとそれに付随する認証機能について説明します。

コンポーネント 認証機能
signup.tsx Emailとパスワードでサインアップを行う
login.tsx Emailとパスワードでログインを行う
sendemail.tsx パスワードの申請メールを受信するために、登録済みのメールアドレスを入力する
passwordReset.tsx パスワードを入力してパスワードの変更を行う
top.tsx ログアウトを行う

サインアップ機能を実装しよう

最初に、サインアップ機能についての説明を行います。
下記がsignup.tsxの全体像です。

signup.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useState } from "react";

export default function SignUp(){

  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [passwordConf, setPasswordConf] = useState("")

  const onSubmit = async(e) => {
    e.preventDefault();
    try{
      const { error:signUpError } = await supabase.auth.signUp({
        email: email,
        password: password,
      })
      if (signUpError) {
        throw signUpError;
      }
    alert('登録完了メールを確認してください');
    }catch(error){
      alert('エラーが発生しました');
    }
  };

  return (
    <>
      <div className={styles.container}>
      <Head>
        <title>新規登録画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
        <form onSubmit={onSubmit}>
        <div>
          <label>メールアドレス</label>
          <input type="email"
            required value={email}
            onChange={e => setEmail(e.target.value)}
          />
        </div>
        <div>
          <label>パスワード</label>
          <input type="password"
            required value={password}
            onChange={e => setPassword(e.target.value)}
          />
        </div>
        <div>
          <label>パスワード確認</label>
          <input type="password"
            required value={passwordConf}
            onChange={e => setPasswordConf(e.target.value)}
          />
        </div>
        <div>
          <button type="submit">サインアップ</button>
        </div>
      </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )
}

では、コードを上から順に追って説明します。

最初にメールアドレス、パスワード、確認用パスワードの各値を状態管理するための記述を行います。

signup.tsx
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [passwordConf, setPasswordConf] = useState("")

次にSupabaseのauthライブラリを用いた、認証部分の実装を行います。
今回は、authライブラリのsignUp関数を使用し、引数にはemailとpasswordを指定します。
signUp関数を使用する方法は、下記のsupabase公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください。

try-catch構文を用いることにより、例外処理を行い、エラーが出た場合の対処を行います。

signup.tsx
 const onSubmit = async(e) => {
    e.preventDefault();
    try{
      const { error:signUpError } = await supabase.auth.signUp({
        email: email,
        password: password,
      })
      if (signUpError) {
        throw signUpError;
      }
     alert('登録完了メールを確認してください');
    }catch(error){
      alert('エラーが発生しました');
    }
  };

signup.tsx全体のマークアップを行います。
メールアドレス、パスワード、確認用パスワードのフォームにonChangeイベントを設定し、入力した各値を保持します。

signup.tsx
return (
    <>
      <div className={styles.container}>
      <Head>
        <title>新規登録画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
        <form onSubmit={onSubmit}>
        <div>
          <label>メールアドレス</label>
          <input type="email"
            required value={email}
            onChange={e => setEmail(e.target.value)}
          />
        </div>
        <div>
          <label>パスワード</label>
          <input type="password"
            required value={password}
            onChange={e => setPassword(e.target.value)}
          />
        </div>
        <div>
          <label>パスワード確認</label>
          <input type="password"
            required value={passwordConf}
            onChange={e => setPasswordConf(e.target.value)}
          />
        </div>
        <div>
          <button type="submit">サインアップ</button>
        </div>
      </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )

下記がマークアップしたサインアップページの画像です。
.

メール認証を設定しよう

上記画像のサインアップボタンを押すと、supabase.auth.signUpが呼び出され、登録したメールアドレス宛てに認証メールを送信することができます。

認証メールを利用したい際には、SupabaseのAuthenticationを選択し、Providersの選択肢からEmailの行を選択します。必要箇所の項目をONに、メール認証が可能になれば、下記画像のように題目行の右端にEnabledマークが出てきます。
スクリーンショット 2023-01-10 2.23.24.png

認証メールの文言はすでにテンプレート素材が設定されていますが、個人で設定したい場合は、Email TemplatesConfirm signupを選択します。Subject headingにはメールの題名、Message bodyにはメール内容を記載します。
スクリーンショット 2023-01-10 9.00.08.png

それでは、実際に画面上でサインアップボタンを押して認証メールが届くか確認してみてます。
サインアップボタンを押すと、登録メール先に下記のようなメールを受信できるはずです。
会員登録完了のリンクを押すと、ユーザー登録が完了し、アプリのルートページにリダイレクトします。
スクリーンショット 2023-01-09 19.49.02.png

また、ユーザ登録が完了しているか確認したい場合は、Supabase AuthentucationUsersを見ましょう。
データが列として入っている場合は、登録が完了しています。まだリンクでの認証が済んでいない場合は、Last Sign Inの列でWaiting gor verificationと黄色で表示されます。
スクリーンショット 2023-01-10 12.56.48.png

ログイン機能を実装しよう

下記がlogin.tsxの全体像です。

login.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useState } from "react";
import Link from 'next/link';
import { useRouter } from 'next/router';

export default function Login(){
  const router = useRouter();
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [passwordConf, setPasswordConf] = useState("")

  const onLogin = async(e) => {
    e.preventDefault();
    try{
      const { error:signInError } = await supabase.auth.signInWithPassword({
        email: email,
        password: password,
      })
      if (signInError) {
        throw signInError;
      }
      await router.push("/top");
    }catch{
      alert('エラーが発生しました');
    }
  }

  return (
    <div className={styles.container}>
      <Head>
        <title>ログイン画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
          <form onSubmit={onLogin}>
          <div>
            <label>メールアドレス</label>
            <input type="email"
              required value={email}
              onChange={e => setEmail(e.target.value)}
            />
          </div>
          <div>
            <label>パスワード</label>
            <input type="password"
              required value={password}
              onChange={e => setPassword(e.target.value)}
            />
          </div>
          <div>
            <label>パスワード確認</label>
            <input type="password"
              required value={passwordConf}
              onChange={e => setPasswordConf(e.target.value)}
            />
          </div>
          <div>
            <button type="submit">ログイン</button><br/>
            <Link href='/signup'>
              ユーザー登録がお済みでない方はこちらから
            </Link><br/>
            <Link href='/sendemail'>
              パスワードをお忘れの方はこちらから
            </Link>
          </div>
        </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
  )
}

ログイン機能の実装方法について、コードを上から順に説明します。
最初にメールアドレス、パスワード、確認用パスワードの各値を状態管理するための記述を行います。

login.tsx
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [passwordConf, setPasswordConf] = useState("")

次にSupabaseのauthライブラリを用いた、認証部分の実装を行います。
今回は、authライブラリのsignInWithPassword関数を使用し、引数にはemailとpasswordを指定します。
また、ログインボタンを押し、ログイン完了したのちトップページにリダイレクトする設定にします。

login.tsx
 const onLogin = async(e) => {
    e.preventDefault();
    try{
      const { error:signInError } = await supabase.auth.signInWithPassword({
        email: email,
        password: password,
      })
      if (signInError) {
        throw signInError;
      }
      await router.push("/top");
    }catch{
      alert('エラーが発生しました');
    }
  }

login.tsx全体のマークアップを行います。
メールアドレス、パスワード、確認用パスワードのフォームにonChangeイベントを設定し、入力した各値を保持します。

login.tsx
 return (
    <div className={styles.container}>
      <Head>
        <title>ログイン画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
          <form onSubmit={onLogin}>
          <div>
            <label>メールアドレス</label>
            <input type="email"
              required value={email}
              onChange={e => setEmail(e.target.value)}
            />
          </div>
          <div>
            <label>パスワード</label>
            <input type="password"
              required value={password}
              onChange={e => setPassword(e.target.value)}
            />
          </div>
          <div>
            <label>パスワード確認</label>
            <input type="password"
              required value={passwordConf}
              onChange={e => setPasswordConf(e.target.value)}
            />
          </div>
          <div>
            <button type="submit">ログイン</button><br/>
            <Link href='/signup'>
              ユーザー登録がお済みでない方はこちらから
            </Link><br/>
            <Link href='/sendemail'>
              パスワードをお忘れの方はこちらから
            </Link>
          </div>
        </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>

下記がマークアップしたログインページの画像です。
.

ログアウト機能を実装しよう

次にログアウト機能の実装方法について説明します。ログアウト機能はtop.tsxに実装しました。
下記がtop.tsxの全体像です。

top.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useRouter } from 'next/router';

export default function Top(){
  const router = useRouter();

  const Logout = async(e) => {
    e.preventDefault();
    try{
      const { error:logoutError } = await supabase.auth.signOut()
      if (logoutError) {
        throw logoutError;
      }
      await router.push("/");
    }catch{
      alert('エラーが発生しました');
    }
  }
  return(
    <>
      <div className={styles.container}>
      <Head>
        <title>トップページ</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
          <h1>トップページ</h1>
          <form onSubmit={Logout}>
            <button type="submit">ログアウトする</button>
          </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )
}

まずはSupabaseのauthライブラリを用いた、認証部分の実装を行います。
今回は、authライブラリのsignOut関数を使用します。
signOut関数を使用する方法は、下記のsupabase公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください。

try-catch構文を用いて例外処理を行い、エラーが出た場合の対処を行います。
また、ログインアウトボタンを押し、ログアウトが完了したのちルートページにリダイレクトする設定にします。

top.tsx
const router = useRouter();

  const Logout = async(e) => {
    e.preventDefault();
    try{
      const { error:logoutError } = await supabase.auth.signOut()
      if (logoutError) {
        throw logoutError;
      }
      await router.push("/");
    }catch{
      alert('エラーが発生しました');
    }
  }

top.tsx全体のマークアップを行います。

top.tsx
return(
    <>
      <div className={styles.container}>
      <Head>
        <title>トップページ</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
          <h1>トップページ</h1>
          <form onSubmit={Logout}>
            <button type="submit">ログアウトする</button>
          </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )

パスワード変更機能を実装しよう

最後にパスワードを忘れた場合を想定して、パスワードリセット機能を実装します。
パスワードをリセットする過程には

  • メールアドレスを登録してパスワードリセット申請メールを受信する
  • 申請メールを確認してリンクを押して、パスワード設定画面に遷移する
  • パスワードを登録する

といった3つの動作が必要です。
上記の動作を行うために申請メールの送信機能、申請メールの設定、パスワードリセット機能の実装を順を追って説明します。

申請メールの送信機能を実装しよう

下記がsendemail.tsxの全体像です。

sendemail.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useState } from "react";

export default function Sendemail(){

  const [email, setEmail] = useState("");

  const onSubmit = async(e) => {
    e.preventDefault();
    try{
      const { error:sendEmailError } =await supabase.auth.resetPasswordForEmail(email, {
        redirectTo: 'http://localhost:3000/passwordReset/',
      });
      if (sendEmailError) {
        throw sendEmailError;
      }
      alert('パスワード設定メールを確認してください');
    }catch(error){
      alert('エラーが発生しました');
    }
  };

  return (
    <>
      <div className={styles.container}>
      <Head>
        <title>パスワードリセット送信画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
        <form onSubmit={onSubmit}>
        <div>
          <label>登録メールアドレス</label>
          <input type="email"
            required value={email}
            onChange={e => setEmail(e.target.value)}
          />
        </div>
        <div>
          <button type="submit">メールを送信</button>
        </div>
      </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )
}

では、最初に申請メール送信機能の実装方法について説明します。
コードを上から順に追って説明します。

最初に、登録するメールアドレスの値を状態管理するための記述を行います。

sendemail.tsx
const [email, setEmail] = useState("");

次に、Supabaseのauthライブラリを用いた、メール送信部分の実装を行います。
今回は、authライブラリのresetPasswordForEmail関数を使用し、引数にはemailとリダイレクト先のURLを指定します。
resetPasswordForEmail関数を使用する方法は、下記のsupabase公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください。

try-catch構文を用いて例外処理を行い、エラーが出た場合の対処を行います。

sendemail.tsx
const onSubmit = async(e) => {
    e.preventDefault();
    try{
      const { error:sendEmailError } =await supabase.auth.resetPasswordForEmail(email, {
        redirectTo: 'http://localhost:3000/passwordReset/',
      });
      if (sendEmailError) {
        throw sendEmailError;
      }
      alert('パスワード設定メールを確認してください');
    }catch(error){
      alert('エラーが発生しました');
    }
  };

sendemail.tsx全体のマークアップを行います。
emailのフォームにonChangeイベントを設定し、入力した値を保持します。

sendemail.tsx
return (
    <>
      <div className={styles.container}>
      <Head>
        <title>パスワードリセット送信画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
        <form onSubmit={onSubmit}>
        <div>
          <label>登録メールアドレス</label>
          <input type="email"
            required value={email}
            onChange={e => setEmail(e.target.value)}
          />
        </div>
        <div>
          <button type="submit">メールを送信</button>
        </div>
      </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )

申請メールを設定しよう

メールを送信のボタンを押すと、supabase.auth.resetPasswordForEmailが呼び出され、登録したメールアドレス宛てに申請メールを送信することができます。

最初に申請メール中にあるリンクのリダイレクト先ドメインを設定します。
このドメインは、sendemail.tsx中に書いてある、メール送信部分のresetPasswordForEmail関数で設定したリダイレクト先のURLドメインを設定するものです。SupabaseのAuthenticationを選択し、URL ConfigurationRedirect URLsにリダイレクト先のドメインを登録します。
スクリーンショット 2023-01-10 11.12.57.png

次に申請メールの文言を設定します。
SupabaseのAuthenticationを選択し、Email TemplatesReset Passwordを選択します。Subject headingにはメールの題名、Message bodyにはメール内容を記載します。
スクリーンショット 2023-01-09 19.39.52.png

それでは、実際に画面上でメールを送信のボタンを押します。
「メールを送信」のボタンを押すと、登録メール先に下記のようなメールを受信します。
Reset Passwordのリンクを押すと、パスワードのリセットが完了し、アプリのパスワードの変更ページにリダイレクトします。
スクリーンショット 2023-01-09 19.58.18.png

パスワードのリセット機能を実装しよう

申請メールのリンクを押して、パスワードの変更登録を行うページにリダイレクトしました。
最後に、パスワードのリセット機能の説明を行います。

下記がpasswordReset.tsxの全体像です。

passwordReset.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useState } from "react";
import { useRouter } from 'next/router';

export default function PasswordReset(){
  const router = useRouter();
  const [password, setPassword] = useState("")
  const [passwordConf, setPasswordConf] = useState("")

  const onSubmit = async(e) => {
    e.preventDefault();
    try{
      const { error:passwordResetError } = await supabase.auth.updateUser({
        password
      });
      if (passwordResetError) {
        throw passwordResetError;
      }
      await router.push("/top");
      alert('パスワード変更が完了しました');
    }catch(error){
      alert('エラーが発生しました');
    }
  };

  return (
    <>
      <div className={styles.container}>
      <Head>
        <title>パスワード再登録画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
        <form onSubmit={onSubmit}>
        <div>
          <label>パスワード</label>
          <input type="password"
            required value={password}
            onChange={e => setPassword(e.target.value)}
          />
        </div>
        <div>
          <label>パスワード確認</label>
          <input type="password"
            required value={passwordConf}
            onChange={e => setPasswordConf(e.target.value)}
          />
        </div>
        <div>
          <button type="submit">パスワード変更</button>
        </div>
      </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )
}

パスワードのリセット機能の実装方法について説明します。

コードを上から順に追って説明します。
最初にパスワード、確認用パスワードの各値を状態管理するための記述を行います。

passwordReset.tsx
const router = useRouter();
const [password, setPassword] = useState("")
const [passwordConf, setPasswordConf] = useState("")

次にSupabaseのauthライブラリを用いた、認証部分の実装を行います。
今回は、authライブラリのupdateUser関数を使用し、引数にはpasswordを指定します。

try-catch構文を用いて例外処理を行い、エラーが出た場合の対処を行います。
パスワードリセット完了後のリダイレクト先をトップページに設定します。

passwordReset.tsx
const onSubmit = async(e) => {
    e.preventDefault();
    try{
      const { error:passwordResetError } = await supabase.auth.updateUser({
        password
      });
      if (passwordResetError) {
        throw passwordResetError;
      }
      await router.push("/top");
    }catch(error){
      alert('エラーが発生しました');
    }
  };

passwordReset.tsx全体のマークアップを行います。
パスワードとパスワード(確認)のフォームにonChangeイベントを設定し、入力した値を保持します。

passwordReset.tsx
return (
    <>
      <div className={styles.container}>
      <Head>
        <title>パスワード再登録画面</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div className={styles.grid}>
        <form onSubmit={onSubmit}>
          <div>
            <label>パスワード</label>
            <input type="password"
              required value={password}
              onChange={e => setPassword(e.target.value)}
            />
          </div>
          <div>
            <label>パスワード確認</label>
            <input type="password"
              required value={passwordConf}
              onChange={e => setPasswordConf(e.target.value)}
            />
          </div>
          <div>
            <button type="submit">パスワード変更</button>
          </div>
        </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )

最後に

これで実装はおしまいです!

今回は、Next.js×supabaseによる認証機能の実装方法について説明しました。
今後は主にフロントエンド周辺、コミュニティ運営、Tech PR(技術広報)の記事を中心に書いていく予定ですので、気になる方はぜひフォローをよろしくお願いします♪ではでは〜

Twitterアカウント:https://twitter.com/kaho_eng

参考資料

30
24
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
24