認証機能の追加
前章では、フォームのバリデーションとアクセシビリティの改善を通じて請求書ルートの構築を完了しました。本章では、ダッシュボードに認証機能を追加します。
認証とは?
認証は現代の多くのウェブアプリケーションにおいて重要な要素です。これはシステムがユーザーが自称する人物であるかを確認する方法です。
安全なウェブサイトでは、通常、ユーザーの身元を確認するために複数の方法を使用します。例えば、ユーザー名とパスワードを入力した後、サイトはデバイスに確認コードを送信したり、Google Authenticatorなどの外部アプリを使用したりすることがあります。この2要素認証(2FA)によりセキュリティが向上します。誰かがパスワードを知ったとしても、ユニークなトークンなしではアカウントにアクセスできません。
認証と認可の違い
ウェブ開発において、認証(Authentication)と認可(Authorization)は異なる役割を果たします:
- 認証はユーザーが自称する人物であることを確認することです。ユーザー名やパスワードなど、ユーザーが持っている情報で身元を証明します。
- 認可はその次のステップです。ユーザーの身元が確認された後、認可はアプリケーションのどの部分を使用できるかを決定します。
つまり、認証は「あなたが誰であるか」を確認し、認可は「アプリケーション内で何ができるか」を決定します。
ログインルートの作成
まず、アプリケーションに/login
という新しいルートを作成し、以下のコードを貼り付けます:
このページでは<LoginForm />
をインポートしていますが、このコンポーネントは後ほど更新します。このコンポーネントはReactの<Suspense>
でラップされています。これは、受信リクエスト(URL検索パラメータ)から情報にアクセスするためです。
NextAuth.js
認証機能を追加するためにNextAuth.jsを使用します。NextAuth.jsは、セッション管理、サインイン/サインアウト、その他の認証関連の複雑な処理の多くを抽象化します。これらの機能を手動で実装することも可能ですが、時間がかかりエラーが発生しやすいです。NextAuth.jsはこのプロセスを簡素化し、Next.jsアプリケーションにおける認証の統一されたソリューションを提供します。
NextAuth.jsのセットアップ
ターミナルで次のコマンドを実行してNextAuth.jsをインストールします:
ここではNext.js 14+と互換性のあるNextAuth.jsのbeta
バージョンをインストールしています。
次に、アプリケーション用のシークレットキーを生成します。このキーはCookieの暗号化に使用され、ユーザーセッションの安全性を確保します。ターミナルで次のコマンドを実行します:
次に、.env
ファイルで、生成したキーをAUTH_SECRET
変数に追加します:
本番環境で認証を機能させるには、Vercelプロジェクトの環境変数も更新する必要があります。Vercelで環境変数を追加する方法については、このガイドを参照してください。
pagesオプションの追加
プロジェクトのルートにauth.config.ts
ファイルを作成し、authConfig
オブジェクトをエクスポートします。このオブジェクトにはNextAuth.jsの設定オプションが含まれます。今のところ、pages
オプションのみ含まれます:
pages
オプションを使用して、カスタムのサインイン、サインアウト、エラーページのルートを指定できます。これは必須ではありませんが、pages
オプションにsignIn: '/login'
を追加することで、ユーザーはNextAuth.jsのデフォルトページではなく、カスタムログインページにリダイレクトされます。
Next.js Middlewareでルートを保護
次に、ルートを保護するロジックを追加します。これにより、ユーザーがログインしていない限り、ダッシュボードページにアクセスできなくなります。
authorized
コールバックは、Next.js Middlewareを使用してページへのリクエストが許可されているかどうかを確認するために使用されます。これはリクエストが完了する前に呼び出され、auth
とrequest
プロパティを含むオブジェクトを受け取ります。auth
プロパティにはユーザーのセッションが含まれ、request
プロパティには受信リクエストが含まれます。
providers
オプションは、さまざまなログインオプションをリストする配列です。今のところ、NextAuthの設定を満たすために空の配列になっています。Credentialsプロバイダーの追加セクションで詳しく学びます。
次に、authConfig
オブジェクトをMiddlewareファイルにインポートする必要があります。プロジェクトのルートにmiddleware.ts
というファイルを作成し、次のコードを貼り付けます:
ここでは、authConfig
オブジェクトでNextAuth.jsを初期化し、auth
プロパティをエクスポートしています。また、Middlewareからmatcher
オプションを使用して、特定のパスで実行するように指定しています。
このタスクにMiddlewareを使用する利点は、認証が確認されるまで保護されたルートのレンダリングが開始されないため、アプリケーションのセキュリティとパフォーマンスの両方が向上することです。
パスワードのハッシュ化
パスワードをデータベースに保存する前にハッシュ化するのは良い習慣です。ハッシュ化により、パスワードはランダムに見える固定長の文字列に変換され、ユーザーデータが漏洩した場合でもセキュリティ層を提供します。
データベースにシードデータを投入する際、bcrypt
パッケージを使用してユーザーのパスワードをハッシュ化してからデータベースに保存しました。本章の後半で、ユーザーが入力したパスワードがデータベースのものと一致するかどうかを比較するために再びこれを使用します。ただし、bcrypt
パッケージ用に別のファイルを作成する必要があります。これは、bcrypt
がNext.js Middlewareでは利用できないNode.js APIに依存しているためです。
authConfig
オブジェクトを展開する新しいauth.ts
ファイルを作成します:
Credentialsプロバイダーの追加
次に、NextAuth.jsのproviders
オプションを追加する必要があります。providers
は、GoogleやGitHubなどのさまざまなログインオプションをリストする配列です。このコースでは、Credentialsプロバイダーのみを使用することに焦点を当てます。
Credentialsプロバイダーでは、ユーザー名とパスワードでログインできます。
豆知識:
OAuthやメールなどの他のプロバイダーもあります。すべてのオプションについてはNextAuth.jsドキュメントを参照してください。
サインイン機能の追加
authorize
関数を使用して認証ロジックを処理できます。Server Actionsと同様に、zod
を使用してメールアドレスとパスワードを検証し、データベースにユーザーが存在するかどうかを確認します:
資格情報を検証した後、データベースからユーザーをクエリする新しいgetUser
関数を作成します。
次に、bcrypt.compare
を呼び出してパスワードが一致するかどうかを確認します:
最後に、パスワードが一致する場合はユーザーを返し、一致しない場合はユーザーがログインできないようにnull
を返します。
ログインフォームの更新
認証ロジックをログインフォームに接続する必要があります。actions.ts
ファイルにauthenticate
という新しいアクションを作成します。このアクションはauth.ts
からsignIn
関数をインポートする必要があります:
'CredentialsSignin'
エラーが発生した場合、適切なエラーメッセージを表示します。NextAuth.jsのエラーについてはドキュメントで学べます。
最後に、login-form.tsx
コンポーネントでReactのuseActionState
を使用してサーバーアクションを呼び出し、フォームエラーを処理し、フォームの保留状態を表示できます:
ログアウト機能の追加
<SideNav />
にログアウト機能を追加するには、<form>
要素内でauth.ts
のsignOut
関数を呼び出します:
試してみる
実際に試してみてください。以下の認証情報を使用してアプリケーションにログイン/ログアウトできるはずです:
- メールアドレス:
[email protected]
- パスワード:
123456