認証
アプリケーションのデータを保護するために、認証の理解は非常に重要です。このページでは、認証を実装するために使用するReactとNext.jsの機能について説明します。
始める前に、このプロセスを3つの概念に分解すると理解しやすくなります:
- 認証:ユーザーが本人であることを確認します。ユーザー名とパスワードなど、ユーザーが持っているものによってアイデンティティを証明する必要があります。
- セッション管理:リクエスト間でユーザーの認証状態を追跡します。
- 認可:ユーザーがアクセスできるルートとデータを決定します。
この図は、ReactとNext.jsの機能を使用した認証フローを示しています:
このページの例では、教育目的でベーシックなユーザー名とパスワードによる認証を説明します。カスタム認証ソリューションを実装することもできますが、セキュリティと簡便性を高めるために、認証ライブラリの使用を推奨します。これらのライブラリは、認証、セッション管理、認可のためのビルトインソリューションに加えて、ソーシャルログイン、多要素認証、ロールベースのアクセス制御などの追加機能を提供します。認証ライブラリセクションでリストを確認できます。
Reactの<form>
要素とServer Actions、そしてuseFormState
を使用して、ユーザーの認証情報を取得し、フォームフィールドを検証し、認証プロバイダーのAPIまたはデータベースを呼び出すことができます。
Server Actionsは常にサーバー上で実行されるため、認証ロジックを処理するための安全な環境を提供します。
サインアップ/ログイン機能を実装するためのステップは以下の通りです:
ユーザーの認証情報を取得するために、送信時にServer Actionを呼び出すフォームを作成します。例えば、ユーザーの名前、メール、パスワードを受け付けるサインアップフォーム:
Server Actionを使用して、サーバー側でフォームフィールドを検証します。認証プロバイダーがフォームバリデーションを提供していない場合は、ZodやYupなどのスキーマバリデーションライブラリを使用できます。
Zodを例として使用すると、適切なエラーメッセージを含むフォームスキーマを定義できます:
認証プロバイダーのAPIやデータベースへの不要な呼び出しを防ぐために、フォームフィールドが定義されたスキーマと一致しない場合は、Server Actionで早期にreturn
することができます。
<SignupForm />
に戻り、ReactのuseFormState
フックを使用して、フォーム送信中にバリデーションエラーを表示できます:
補足:
- これらの例では、Next.js App Routerにバンドルされているreactの
useFormState
フックを使用しています。React 19を使用している場合は、代わりにuseActionState
を使用してください。詳細についてはReactのドキュメントを参照してください。
- React 19では、
useFormStatus
は返されるオブジェクトにdata、method、actionなどの追加のキーを含みます。React 19を使用していない場合は、pending
キーのみが利用可能です。
- React 19では、
useActionState
も返されるstateにpending
キーを含みます。
- データを変更する前に、ユーザーがそのアクションを実行する権限を持っているかを必ず確認する必要があります。認証と認可を参照してください。
フォームフィールドを検証した後、認証プロバイダーのAPIまたはデータベースを呼び出して、新しいユーザーアカウントを作成するか、ユーザーが存在するかを確認できます。
前の例から続けて:
ユーザーアカウントの作成または認証情報の確認に成功した後、セッションを作成してユーザーの認証状態を管理できます。セッション管理戦略に応じて、セッションをクッキーやデータベース、または両方に保存できます。詳細についてはセッション管理セクションを参照してください。
ヒント:
- 上記の例は教育目的で認証のステップを詳しく説明しているため冗長になっています。これは、独自の安全なソリューションの実装がいかに複雑になりうるかを示しています。プロセスを簡略化するために認証ライブラリの使用を検討してください。
- ユーザー体験を向上させるため、登録フロー中の早い段階で重複するメールアドレスやユーザー名をチェックすることをお勧めします。例えば、ユーザーが入力中やインプットフィールドがフォーカスを失った時などです。これにより不要なフォーム送信を防ぎ、ユーザーにすぐにフィードバックを提供できます。これらのチェックの頻度を管理するために、use-debounceなどのライブラリでデバウンスできます。
セッション管理は、リクエスト間でユーザーの認証状態が保持されることを保証します。セッションまたはトークンの作成、保存、更新、削除を含みます。
セッションには2つのタイプがあります:
- ステートレス:セッションデータ(またはトークン)はブラウザのクッキーに保存されます。クッキーは各リクエストと共に送信され、サーバー上でセッションを検証できます。このメソッドはより簡単ですが、正しく実装されない場合はセキュリティが低下する可能性があります。
- データベース:セッションデータはデータベースに保存され、ユーザーのブラウザは暗号化されたセッションIDのみを受け取ります。このメソッドはより安全ですが、複雑でサーバーリソースを多く使用する可能性があります。
補足: どちらのメソッドも使用できますし、両方を組み合わせることもできますが、iron-sessionやJoseなどのセッション管理ライブラリの使用を推奨します。
ステートレスセッションを作成および管理するには、以下のステップを実行する必要があります:
- セッションの署名に使用するシークレットキーを生成し、環境変数として保存します。
- セッション管理ライブラリを使用してセッションデータを暗号化/復号化するロジックを記述します。
- Next.jsの
cookies
APIを使用してクッキーを管理します。
上記に加えて、ユーザーがアプリケーションに戻ってきたときにセッションを更新(またはリフレッシュ)し、ログアウト時にセッションを削除する機能を追加することを検討してください。
補足: 認証ライブラリにセッション管理が含まれているかどうかを確認してください。
セッションに署名するためのシークレットキーを生成する方法はいくつかあります。例えば、ターミナルでopenssl
コマンドを使用することもできます:
このコマンドは32文字のランダムな文字列を生成し、それをシークレットキーとして環境変数ファイルに保存できます:
そして、このキーをセッション管理ロジックで参照できます:
次に、好みのセッション管理ライブラリを使用してセッションを暗号化および復号化できます。前の例から続けて、Edge Runtimeと互換性のあるJoseとReactのserver-only
パッケージを使用して、セッション管理ロジックがサーバーでのみ実行されることを保証します。
ヒント:
- ペイロードには、後続のリクエストで使用される最小限のユニークなユーザーデータ(ユーザーIDやロールなど)のみを含める必要があります。電話番号、メールアドレス、クレジットカード情報などの個人を特定できる情報や、パスワードなどの機密データを含めるべきではありません。
セッションをクッキーに保存するには、Next.jsのcookies
APIを使用します。クッキーはサーバーで設定され、推奨オプションを含める必要があります:
- HttpOnly: クライアントサイドのJavaScriptがクッキーにアクセスするのを防ぎます。
- Secure: クッキーの送信にhttpsを使用します。
- SameSite: クロスサイトリクエストでクッキーを送信できるかどうかを指定します。
- Max-Age または Expires: 一定期間後にクッキーを削除します。
- Path: クッキーのURLパスを定義します。
これらのオプションの詳細についてはMDNを参照してください。
Server Actionに戻り、createSession()
関数を呼び出し、redirect()
APIを使用してユーザーを適切なページにリダイレクトできます:
ヒント:
- クッキーはサーバーで設定される必要があります。クライアントサイドでの改ざんを防ぐためです。
- 🎥 Next.jsでのステートレスセッションと認証について詳しく学ぶ → YouTube (11分)。
セッションの有効期限を延長することもできます。これはユーザーがアプリケーションに再度アクセスした後もログイン状態を維持するのに役立ちます。例えば:
ヒント: 認証ライブラリがリフレッシュトークンをサポートしているかどうかを確認してください。リフレッシュトークンを使用してユーザーのセッションを延長できます。
セッションを削除するには、クッキーを削除できます:
そしてdeleteSession()
関数をアプリケーションで再利用できます。例えば、ログアウト時:
データベースセッションを作成および管理するには、以下のステップを実行する必要があります:
- セッションとデータを保存するためのテーブルをデータベースに作成します(または認証ライブラリがこれを処理するかどうかを確認します)。
- セッションの挿入、更新、削除の機能を実装します。
- セッションIDをユーザーのブラウザに保存する前に暗号化し、データベースとクッキーが同期していることを確認します(これはオプションですが、Middlewareでの楽観的な認証チェックのために推奨されます)。
例えば:
ヒント:
- より高速なデータ取得のために、Vercel Redisのようなデータベースの使用を検討してください。ただし、セッションデータをプライマリデータベースに保持し、クエリ数を減らすためにデータリクエストを組み合わせることもできます。
- ユーザーの最終ログイン時刻の追跡や、アクティブなデバイス数の追跡、すべてのデバイスからログアウトする機能をユーザーに提供するなど、より高度なユースケースにはデータベースセッションを使用することを選択できます。
セッション管理を実装した後、ユーザーがアプリケーション内で何にアクセスし、何ができるかを制御する認可ロジックを追加する必要があります。詳細については認可セクションを参照してください。
ユーザーが認証され、セッションが作成されると、ユーザーがアプリケーション内で何にアクセスし、何ができるかを制御する認可を実装できます。
認可チェックには2つの主要なタイプがあります:
- 楽観的: クッキーに保存されたセッションデータを使用して、ユーザーがルートにアクセスしたりアクションを実行する権限があるかどうかをチェックします。これらのチェックは、UIコンポーネントの表示/非表示や、権限やロールに基づいたユーザーのリダイレクトなどの迅速な操作に役立ちます。
- 安全: データベースに保存されたセッションデータを使用して、ユーザーがルートにアクセスしたりアクションを実行する権限があるかどうかをチェックします。これらのチェックはより安全で、機密データやアクションへのアクセスが必要な操作に使用されます。
どちらの場合も、以下を推奨します:
Middlewareを使用して権限に基づいてユーザーをリダイレクトしたい場合があります:
- 楽観的チェックを実行するため。Middlewareはすべてのルートで実行されるため、リダイレクトロジックを一元化し、権限のないユーザーを事前にフィルタリングするのに適しています。
- ユーザー間で共有される静的ルート(例:有料コンテンツ)を保護するため。
しかし、Middlewareはプリフェッチされたルートを含むすべてのルートで実行されるため、パフォーマンスの問題を防ぐために、クッキーからセッションを読み取る(楽観的チェック)のみを行い、データベースチェックを避けることが重要です。
例えば:
Middlewareは初期チェックに役立ちますが、データを保護する唯一の防衛線であってはなりません。セキュリティチェックの大部分は、データソースにできるだけ近い場所で実行される必要があります。詳細についてはデータアクセスレイヤーを参照してください。
ヒント:
- Middlewareでは、
req.cookies.get('session').value
を使用してクッキーを読み取ることもできます。
- MiddlewareはEdge Runtimeを使用します。認証ライブラリとセッション管理ライブラリが互換性があるかどうかを確認してください。
- Middlewareの
matcher
プロパティを使用して、Middlewareを実行するルートを指定できます。ただし、認証の場合は、Middlewareがすべてのルートで実行されることを推奨します。
データリクエストと認可ロジックを一元化するためにDALを作成することを推奨します。
DALには、ユーザーがアプリケーションを操作する際にユーザーのセッションを検証する関数を含める必要があります。最低でも、その関数はセッションが有効かどうかをチェックし、その後のリクエストに必要なユーザー情報を返すか、ユーザーをリダイレクトする必要があります。
例えば、verifySession()
関数を含むDALのための別ファイルを作成します。そしてReactのcache APIを使用して、React のレンダリングパス中に関数の戻り値をメモ化します:
そして、データリクエスト、Server Actions、Route HandlersでverifySession()
関数を呼び出すことができます:
ヒント:
- DALはリクエスト時に取得されるデータを保護するために使用できます。ただし、ユーザー間で共有されるデータを持つ静的ルートの場合、データはビルド時に取得され、リクエスト時ではありません。静的ルートを保護するにはMiddlewareを使用してください。
- 安全なチェックのために、セッションIDをデータベースと比較してセッションが有効かどうかを確認できます。レンダリングパス中に不要な重複したデータベースリクエストを避けるために、Reactのcache関数を使用してください。
- 関連するデータリクエストを、
verifySession()
を実行してから任意のメソッドを実行するJavaScriptクラスにまとめることもできます。
データを取得する際は、アプリケーションで使用される必要なデータのみを返し、オブジェクト全体は返さないことを推奨します。例えば、ユーザーデータを取得する場合、パスワードや電話番号などを含むユーザーオブジェクト全体ではなく、ユーザーのIDと名前のみを返すことができます。
ただし、返されるデータ構造を制御できない場合や、クライアントに全体のオブジェクトが渡されることを避けたいチームで作業している場合は、クライアントに公開しても安全なフィールドを指定するなどの戦略を使用できます。
DALでデータリクエストと認可ロジックを一元化し、DTOを使用することで、すべてのデータリクエストが安全で一貫していることを保証でき、アプリケーションの規模が大きくなるにつれて保守、監査、デバッグがより容易になります。
補足:
- DTOを定義する方法はいくつかあります。上記の例のような個別の関数から、
toJSON()
の使用、JavaScriptクラスまで様々です。これらはJavaScriptのパターンであり、ReactやNext.jsの機能ではないため、アプリケーションに最適なパターンを見つけるための調査を推奨します。
- セキュリティのベストプラクティスについては、Next.jsのセキュリティに関する記事で詳しく学べます。
Server Componentsでの認証チェックは、ロールベースのアクセスに役立ちます。例えば、ユーザーのロールに基づいてコンポーネントを条件付きでレンダリングする場合:
この例では、DALのverifySession()
関数を使用して'admin'、'user'、および未認証のロールをチェックしています。このパターンにより、各ユーザーが自分のロールに適したコンポーネントとのみやり取りできることが保証されます。
部分レンダリングにより、レイアウトでのチェックには注意が必要です。これらはナビゲーション時に再レンダリングされないため、ユーザーセッションはルート変更のたびにチェックされません。
代わりに、データソースや条件付きでレンダリングされるコンポーネントの近くでチェックを行う必要があります。
例えば、ユーザーデータを取得し、ナビゲーションにユーザー画像を表示する共有レイアウトを考えてみましょう。レイアウトで認証チェックを行うのではなく、レイアウトでユーザーデータ(getUser()
)を取得し、DALで認証チェックを行うべきです。
これにより、アプリケーション内でgetUser()
が呼び出されるたびに認証チェックが実行されることが保証され、開発者がデータへのアクセス権限をチェックするのを忘れることを防ぎます。
補足:
- SPAでよくみられるパターンは、ユーザーが認証されていない場合にレイアウトや最上位コンポーネントで
return null
を返すことです。このパターンは推奨されません。Next.jsアプリケーションには複数のエントリーポイントがあり、ネストされたルートセグメントとServer Actionsへのアクセスを防ぐことはできないためです。
Server Actionsは公開されているAPIエンドポイントと同じセキュリティ上の考慮事項で扱い、ユーザーがミューテーションを実行する権限があるかどうかを確認する必要があります。
以下の例では、アクションを進める前にユーザーのロールをチェックしています:
Route Handlersは公開されているAPIエンドポイントと同じセキュリティ上の考慮事項で扱い、ユーザーがRoute Handlerにアクセスする権限があるかどうかを確認する必要があります。
例えば:
上記の例は、2段階のセキュリティチェックを持つRoute Handlerを示しています。まずアクティブなセッションをチェックし、その後ログインしているユーザーが'admin'であるかを確認します。
認証用のコンテキストプロバイダーはインターリービングのおかげで動作します。ただし、React context
はServer Componentsではサポートされておらず、Client Componentsでのみ適用可能です。
これは動作しますが、子のServer Componentsは最初にサーバーでレンダリングされ、コンテキストプロバイダーのセッションデータにアクセスできません:
セッションデータがClient Componentsで必要な場合(例:クライアントサイドのデータフェッチのため)、ReactのtaintUniqueValue
APIを使用して機密性の高いセッションデータがクライアントに露出するのを防ぎます。
Next.jsでの認証について学んだので、安全な認証とセッション管理を実装するためのNext.js互換のライブラリとリソースを紹介します:
認証とセキュリティについて学び続けるために、以下のリソースをチェックしてください: