Next.js と TypeScript で、認証の実装方法を理解する

TwitterFacebookHatena

TL;DR

このページでは、Next.js での認証の実装方法について解説します。認証はユーザーが誰であるかを確認するプロセスで、Next.js ではさまざまな認証パターンをサポートしています。それぞれのパターンは異なるユースケースに対応して設計されています。

この記事は、Next.js の公式ドキュメンテーションを参考について解説しています。特に、Authenticatingのセクションを基に、その内容を理解しやすく解釈し直し、さらに詳細な説明を加えています。

この記事は、Next.js の公式ドキュメンテーションの素晴らしい内容を尊重し、その知識を広めることを目指しています。そのため、この記事の内容は、公式ドキュメンテーションの内容をそのまま意訳するのではなく、それを基に私たち自身の理解と経験を元にした解釈と説明を加えています。

もし、より詳細な情報や公式の説明を求める場合は、Next.js の公式ドキュメンテーションを直接ご覧いただくことを強く推奨します。この記事の解説はあくまで参考の一つであり、公式ドキュメンテーションには、より詳細な情報や最新の更新情報が含まれています。

開発環境 バージョン
Next.js 13.4.4(Pages Router)
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

認証とは

認証は、ユーザーが誰であるかを確認するプロセスです。一方、認可はユーザーがアクセスできる内容を制御します。Next.js はさまざまな認証パターンをサポートしており、それぞれが異なるユースケースに対応するように設計されています。

認証パターン

認証パターンを選ぶときの最初のステップは、あなたがどのようにデータを取得したいかを理解することです。これを「データフェッチング戦略」と呼びます。それを理解したら、どの認証プロバイダ(認証を手助けしてくれるツールやサービス)がその戦略をサポートしているかを決められます。主なパターンは 2 つあります:

その 1:静的生成を使用してローディング状態をサーバーレンダリングし、その後クライアントサイドでユーザーデータを取得する。

これは、まずサーバーでページを作成し、そのページに「ローディング...」のようなメッセージを表示します。そして、そのページがユーザーのブラウザに表示された後で、JavaScript を使ってユーザーのデータを取得します。これは、ページがすぐに表示され、その後でデータが更新されるという流れになります。

その 2:ユーザーデータをサーバーサイドで取得し、認証されていないコンテンツのフラッシュを排除する。

これは、サーバーでページを作成するときに、すぐにユーザーデータを取得します。その結果、ページがユーザーのブラウザに表示されるときには、すでにすべてのデータが揃っています。これにより、「認証されていないコンテンツのフラッシュ」、つまり、一瞬だけログインしていないと思われる状態が表示されることを防ぐことができます。

静的生成されたページの認証

Next.js は、ページが特別なデータをすぐに必要としない場合、そのページは静的(あらかじめ作られている)だと自動的に判断します。これは、ページに getServerSidePropsgetInitialProps という特別な関数がないことを意味します。その代わり、あなたのページは最初に「ローディング中...」のような状態を表示し、その後、ユーザーのブラウザでユーザーデータを取得します。

この方法の良い点は、ページが世界中のどこからでもすぐに取得でき、ページの表示が早くなることです。具体的には、ユーザーがページと対話できるまでの時間が短くなります。

例えば、プロフィールページを考えてみましょう。このページは最初に「ローディング中...」のような状態を表示します。そして、ユーザーデータの取得が完了すると、ユーザーの名前が表示されます。

// pages/profile.js
import useUser from '../lib/useUser'
import Layout from '../components/Layout'

const Profile = () => {
  // クライアントサイドでユーザーを取得します
  const { user } = useUser({ redirectTo: '/login' })

  // サーバーでローディング状態をレンダリングします
  if (!user || user.isLoggedIn === false) {
    return <Layout>Loading...</Layout>
  }

  // ユーザーのリクエストが完了したら、ユーザーを表示します
  return (
    <Layout>
      <h1>Your Profile</h1>
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </Layout>
  )
}

export default Profile

サーバーレンダリングされたページの認証

ページから getServerSideProps という非同期関数をエクスポートすると、Next.js は各リクエストでこのページを getServerSideProps によって返されたデータを使用して事前レンダリングします。

export async function getServerSideProps(context) {
  return {
    props: {}, // Will be passed to the page component as props
  }
}

プロフィールの例をサーバーサイドレンダリングを使用するように変更してみましょう。セッションがある場合、ユーザーをページのプロフィールコンポーネントのプロップとして返します。この例ではローディングスケルトンはありません。

// pages/profile.js
import withSession from '../lib/session'
import Layout from '../components/Layout'

// サーバーサイドでユーザーデータを取得します
export const getServerSideProps = withSession(async function ({ req, res }) {
  // セッションからユーザーデータを取得します
  const { user } = req.session

  // ユーザーデータがない場合、ログインページにリダイレクトします
  if (!user) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    }
  }

  // ユーザーデータがある場合、そのデータをページのプロップとして返します
  return {
    props: { user },
  }
})

const Profile = ({ user }) => {
  // ユーザーデータを表示します。ローディング状態は必要ありません
  return (
    <Layout>
      <h1>Your Profile</h1>
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </Layout>
  )
}

export default Profile

このパターンの利点は、リダイレクトする前に認証されていないコンテンツのフラッシュを防ぐことです。getServerSideProps でユーザーデータをフェッチすると、認証プロバイダへのリクエストが解決するまでレンダリングがブロックされることに注意してください。ボトルネックを作成し、TTFB(Time to First Byte、最初のバイトまでの時間)を増加させないように、認証ルックアップが高速であることを確認する必要があります。そうでなければ、静的生成を検討してください。

認証プロバイダ

Google、Facebook、GitHub などの組み込みプロバイダ、JWT、JWE、メール/パスワード、マジックリンクなどを含むフル機能の認証システムが必要な場合は、next-auth を使用します。

Next-Auth

Next-Auth は、Next.js のためのフル機能の認証ソリューションです。Google、Facebook、GitHub などの組み込みプロバイダ、JWT、JWE、メール/パスワード、マジックリンクなどをサポートしています。

Next-Auth は、どちらの認証パターンもサポートしています。つまり、静的生成を使用してクライアントサイドでユーザーデータをフェッチすることも、サーバーサイドでユーザーデータをフェッチして認証されていないコンテンツのフラッシュを排除することも可能です。

Next-Auth を使用すると、認証プロバイダへのリクエストが解決するまでレンダリングがブロックされることに注意してください。認証ルックアップが高速であることを確認する必要があります。そうでなければ、静的生成を検討してください。

以下に Next-Auth の基本的な実装例を示します。

// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

export default NextAuth({
  // プロバイダの設定
  providers: [
    Providers.GitHub({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    // 他のプロバイダも追加可能
  ],

  // データベースの設定
  database: process.env.DATABASE_URL,

  // セッションの設定
  session: {
    jwt: true,
  },

  // JWTの設定
  jwt: {
    secret: process.env.JWT_SECRET,
  },
})

このコードは、pages/api/auth/[...nextauth].jsというファイルに配置します。Next-Auth は、GitHub を認証プロバイダとして使用する設定を示しています。clientIdclientSecretは、GitHub の OAuth アプリケーションから取得します。

また、この設定では JWT(JSON Web Tokens)を使用してセッションを管理しています。これは、ユーザーの認証情報を安全に保存するための方法です。jwt.secretは、JWT を暗号化するための秘密鍵です。

データベースの設定では、process.env.DATABASE_URLを使用してデータベースへの接続を設定します。これは、あなたのデータベースへの接続文字列を環境変数から取得します。

この設定を使用すると、Next-Auth は/api/auth/signin/api/auth/signout/api/auth/sessionなどの API ルートを自動的に作成します。これらのルートは、ユーザーの認証とセッション管理に使用されます。

認証機能を含むディレクトリツリー

Next-Auth を組み込むときの基本的なディレクトリツリーは以下のようになります。ユーザープロフィールと CRUD(Create, Read, Update, Delete)操作を含む場合、それぞれの機能に対応するページや API エンドポイントを追加します。

.
├── pages
│   ├── api
│   │   ├── auth
│   │   │   └── [...nextauth].js  // Next-Authの設定ファイル
│   │   ├── user
│   │   │   ├── index.js          // ユーザー一覧(Read)
│   │   │   └── [id].js           // 特定のユーザーに対するCRUD操作
│   ├── profile
│   │   └── index.js              // ユーザープロフィールページ
│   ├── create.js                 // ユーザー作成(Create)ページ
│   ├── update.js                 // ユーザー更新(Update)ページ
│   └── delete.js                 // ユーザー削除(Delete)ページ
├── lib
│   └── session.js                // セッション管理用のライブラリ
├── components
│   ├── Layout.js                 // 共通レイアウトコンポーネント
│   └── UserProfile.js            // ユーザープロフィール表示コンポーネント
├── .env.local                    // 環境変数
├── package.json
└── next.config.js

この構成では、/api/user/api/user/[id]のエンドポイントを使用してユーザーデータの CRUD 操作を行います。/api/userはユーザー一覧を取得するために使用し、/api/user/[id]は特定のユーザーに対する操作(取得、更新、削除)を行います。

また、ユーザープロフィールページは/profileに配置します。このページでは、ログイン中のユーザーのプロフィール情報を表示します。

ユーザーの作成、更新、削除の各操作に対応するページも提供します。これらのページでは、ユーザー情報の入力フォームと、それに対応する API へのリクエストを行う機能を提供します。

環境変数

Next-Auth を使用する際には、環境変数を設定することが一般的です。環境変数は、アプリケーションの設定や秘密情報を管理するために使用されます。

Next-Auth では、以下のような情報を環境変数として設定することがあります。

  1. 認証プロバイダのクライアント ID とクライアントシークレット: Next-Auth を使用して OAuth プロバイダ(Google、Facebook、GitHub など)で認証を行う場合、クライアント ID とクライアントシークレットを環境変数として設定します。これらの値は、プロバイダから取得します。

  2. データベース URL: Next-Auth を使用してデータベースに接続する場合、データベースへの接続文字列を環境変数として設定します。

  3. JWT の秘密鍵: Next-Auth を使用して JWT(JSON Web Tokens)を使用したセッション管理を行う場合、JWT を暗号化するための秘密鍵を環境変数として設定します。

これらの環境変数は、.env.localファイルに保存することが一般的です。このファイルは、アプリケーションのルートディレクトリに配置します。Next.js は、デフォルトで.env.localファイルから環境変数を読み込みます。

環境変数を使用することで、秘密情報をソースコードから分離し、セキュリティを向上させることができます。また、環境変数を使用することで、開発環境と本番環境で異なる設定を簡単に行うことができます。

Next-Auth の設定に関連する環境変数の一例を以下に示します。これらの値は.env.localファイルに保存します。

# .env.local

# GitHub OAuthの設定
GITHUB_ID=your_github_client_id
GITHUB_SECRET=your_github_client_secret

# データベース接続文字列
DATABASE_URL=your_database_connection_string

# JWTの秘密鍵
JWT_SECRET=your_jwt_secret

上記のyour_github_client_idyour_github_client_secretyour_database_connection_stringyour_jwt_secretは、それぞれ実際の値に置き換えてください。

  • GITHUB_IDGITHUB_SECRETは、GitHub の OAuth アプリケーションから取得します。これらの値は GitHub で認証を行うために使用します。

  • DATABASE_URLは、データベースへの接続文字列です。この値は、Next-Auth がデータベースに接続するために使用します。

  • JWT_SECRETは、JWT(JSON Web Tokens)を暗号化するための秘密鍵です。この値は、Next-Auth が JWT を使用したセッション管理を行うために使用します。

なお、.env.localファイルは公開リポジトリには含めないように注意してください。このファイルは.gitignoreファイルに追加し、Git の管理対象から除外します。

Tips

Next-Auth を組み込む際には、以下のような Tips が役立つかもしれません。

  1. 環境変数の管理: Next-Auth の設定には、クライアント ID、クライアントシークレット、データベース URL など、秘密情報を含む環境変数が多く使用されます。これらの情報は.env.localファイルなどに保存し、公開されないように管理することが重要です。

  2. セッション管理: Next-Auth は、JWT(JSON Web Tokens)を使用したセッション管理をサポートしています。これにより、ユーザーの認証情報を安全に保存することができます。ただし、JWT はデータ量が大きくなるとパフォーマンスに影響を与える可能性があるため、必要な情報のみを保存するように注意してください。

  3. プロバイダの選択: Next-Auth は、多くの認証プロバイダ(Google、Facebook、GitHub など)をサポートしています。プロジェクトの要件に応じて、最適なプロバイダを選択してください。

  4. エラーハンドリング: Next-Auth の API ルートは、認証に関連するエラーを返す可能性があります。これらのエラーを適切にハンドリングし、ユーザーに適切なフィードバックを提供することが重要です。

Next.js と TypeScript で、認証の実装方法を理解する