💭

CognitoとNextAuthで認証基盤を作ってみる

2025/01/22に公開

本記事では、認証とは何か? という基本概念から、AWS Cognitoを利用した認証フローの図解、トークン管理のベストプラクティス、そしてNextAuth.jsを使った具体的な実装例までをまとめています。


1. 認証とは?

認証(Authentication) とは、「ユーザーが本当に本人であるかどうか」を確認する仕組みです。

  • ユーザー名とパスワードによる認証が最も一般的ですが、現在では多要素認証(MFA/2FA)や、スマホ等での生体認証(指紋・顔認証など)を活用するケースが増えています。
  • 認証で「誰であるか」を特定したあと、特定されたユーザーが「何ができるか」を決めるプロセスは**認可(Authorization)**と呼ばれます。

:::info
: 銀行のATMを想像してください。

  • 認証: カードと暗証番号を使って「本人かどうか」を確かめる。
  • 認可: その後の「入金・出金・振込など各種操作ができるか」を決定する。
    :::

2. AWS Cognitoとは?

AWS Cognitoは、AWSが提供する認証・認可基盤のサービスです。主に**ユーザープール(User Pool)IDプール(Identity Pool)**という2つの機能を使い分けることで、スケーラブルかつ安全なユーザー管理が行えます。

ユーザープール(User Pool)

  • 役割: ユーザー認証(Authentication)
  • 主な機能:
    • ユーザー登録・ログイン
    • パスワードポリシーやMFA設定
    • ソーシャルログイン(Google、Facebookなど)
    • トークン(ID/アクセス/リフレッシュ)の発行

IDプール(Identity Pool)

  • 役割: ユーザー認可(Authorization)
  • 主な機能:
    • 一時的なAWS認証情報(IAMロール)の払い出し
    • S3、DynamoDBなどAWSリソースへのアクセス管理
    • 複数の認証プロバイダー(CognitoやGoogle等)と連携

:::info
ユーザープール vs. IDプール

機能 ユーザープール (User Pool) IDプール (Identity Pool)
主な役割 認証(誰であるか 認可(何ができるか
出力 各種トークン(ID/アクセス/リフレッシュ) AWS認証情報(IAMロール)
用途 ユーザー登録・ログイン AWSリソースへのアクセスコントロール
:::

3. 認証フロー

AWS Cognitoを用いた一般的な認証フローを、シーケンス図システム構成図の2つで解説します。両方を見ることで「どのタイミングでどんなやり取りが行われているか」と、「どこにどんなコンポーネントが存在するか」がより明確になります。

3-1. シーケンス図

各ステップ解説

  1. ログイン要求: ユーザーがアプリに対してログイン情報(ユーザー名、パスワード等)を送信します。
  2. 認証リクエスト: アプリはCognitoユーザープールに対し、「このユーザーが正しいかどうか」を問い合わせます。
  3. トークン発行: ユーザーが正しければ、CognitoがIDトークン・アクセストークン・リフレッシュトークンをアプリに返却します。
  4. 権限要求: アプリは「IDトークン」を持ってCognitoのIDプールにアクセスし、「どのAWSリソースにアクセスできるか?」を問い合わせます。
  5. AWS認証情報の取得: IDプールがIAMロールを払い出し、アプリに一時的なAWS認証情報を返却します。
  6. AWSリソースアクセス: アプリは取得したIAMロールでS3やDynamoDBなどに直接アクセスし、必要な処理を行います。
  7. レスポンス: AWSリソースからの処理結果がアプリを介してユーザーに返されます。
  8. トークン更新要求: アクセストークンの期限が切れた際、リフレッシュトークンを使って新しいトークンを取得しにいきます。
  9. 新しいトークン取得: ユーザープールから更新後のIDトークンやアクセストークンが再発行され、アプリはそれを使用し続けます。

3-2. システム構成図

図のポイント

  • User Pool: 認証を担当。ユーザーのログイン要求を受け付け、ユーザー情報(UserDB)を照合してトークンを発行します。
  • ID Pool: 認可を担当。ユーザーから提示されたIDトークンを確認し、対応するIAMロールを返却します。
  • AWSリソース: 取得したIAMロールを用いて直接アクセス可能となる対象のサービス(S3, DynamoDBなど)。
  • User: フロントエンドやクライアントの立場。認証フロー(ログイン)と認可フロー(IAMロール取得)の両方を担当アプリを通じて行います。

このように2つの図を組み合わせて見ると、**「どのタイミングでトークンが発行されるのか」「IDプールとユーザープールはそれぞれ何をしているのか」**がイメージしやすくなります。


4. トークン管理

トークンをどこに保存するかはアプリケーションのセキュリティを左右します。
XSSCSRFなどの攻撃に備えて、適切な管理が重要です。

保存場所と理由

アクセストークン

// メモリで保持する例
let accessToken: string | null = null;

function setAccessToken(token: string) {
  accessToken = token;
}

function getAccessToken() {
  return accessToken;
}
  • 理由: 攻撃者に盗まれにくいよう、ブラウザのメモリ内(JavaScript変数)に保持するのが望ましい。

IDトークン

// サーバーサイドでHttpOnly Cookieに保存する例
res.cookie('idToken', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
});
  • 理由: ユーザー情報が含まれるため、JavaScriptから参照できないHttpOnly Cookieでの保存が推奨。

リフレッシュトークン

// サーバーサイドでHttpOnly Cookieに保存
res.cookie('refreshToken', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 30 * 24 * 60 * 60 * 1000 // 30日
});
  • 理由: 有効期限が長いため、最も厳重に扱う必要がある。

セキュリティ上の注意点

  1. LocalStorageへの保存は極力避ける: XSSで容易に盗まれるリスク。
  2. HttpOnly Cookieを活用: JavaScriptからの読み取りを防ぎ、盗難リスクを低減。
  3. CSRF対策: SameSite 属性やCSRFトークンで不正リクエストを防ぐ。
  4. 定期的なトークンローテーション: リフレッシュトークンも含め有効期限を短めに設定し、流出リスクを抑える。

5. NextAuthとは?

NextAuth.js は、Next.js 用の認証ライブラリで、以下の特徴を持ちます。

  1. 多様なプロバイダーに対応: Cognitoだけでなく、GoogleやGitHubなど一般的なOAuthプロバイダーとの連携を簡単に行える。
  2. セッション管理の抽象化: JWTベースまたはCookieベースのセッション管理を選択できる。
  3. カスタマイズ可能なコールバック: 認証後のトークン加工やセッションの拡張など柔軟な設定が可能。
  4. ミドルウェアとの併用: Next.jsのミドルウェア機能と組み合わせて、特定のページへのアクセスを制限するなど高度な制御が容易。

6. NextAuthを使った実装サンプル

以下は、AWS CognitoNextAuth.jsを組み合わせた基本実装の例です。

(1) NextAuth設定ファイル

pages/api/auth/[...nextauth].ts

import NextAuth from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';

export default NextAuth({
  providers: [
    CognitoProvider({
      clientId: process.env.COGNITO_CLIENT_ID!,
      clientSecret: process.env.COGNITO_CLIENT_SECRET!,
      issuer: process.env.COGNITO_ISSUER!, // ユーザープールのドメイン
    }),
  ],
  session: {
    strategy: "jwt",
  },
  callbacks: {
    async jwt({ token, account }) {
      // サインイン時に取得したアクセストークンをJWTに格納
      if (account) {
        token.accessToken = account.access_token;
      }
      return token;
    },
    async session({ session, token }) {
      // アクセストークンなどをセッション情報に付与
      session.accessToken = token.accessToken;
      return session;
    },
  },
});
  • providersCognitoProvider を設定し、CognitoのクライアントIDやシークレットを登録。
  • callbacks で認証後のトークンやセッションを加工することが可能。

(2) ミドルウェアでアクセス制限

middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export default function middleware(request: NextRequest) {
  const path = request.nextUrl.pathname;
  
  // 認証が必要なページの例: /dashboard/
  if (path.startsWith('/dashboard')) {
    const token = request.cookies.get('next-auth.session-token')
               || request.cookies.get('__Secure-next-auth.session-token');
    if (!token) {
      return NextResponse.redirect(new URL('/api/auth/signin', request.url));
    }
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*'],
};
  • **matcher**でミドルウェアを実行するパスを指定。
  • トークンが無い場合はサインイン画面へリダイレクト。

(3) ログイン・ログアウトボタンの例

import { signIn, signOut, useSession } from 'next-auth/react';

export default function AuthButton() {
  const { data: session } = useSession();

  if (!session) {
    return <button onClick={() => signIn('cognito')}>Sign in with Cognito</button>;
  }

  return (
    <div>
      <p>Signed in as {session.user?.email}</p>
      <button onClick={() => signOut()}>Sign out</button>
    </div>
  );
}
  • signIn('cognito') でCognitoを使用したログインフローが開始。
  • signOut() でセッションが破棄され、ログアウト。

7. まとめ

  1. 認証とは?

    • ユーザーが「本当に本人か」を確かめるプロセス。
    • パスワード認証だけでなく、MFA・生体認証などを組み合わせるとセキュリティが高まります。
  2. AWS Cognitoとは?

    • ユーザープールで認証を、IDプールで認可を担当するAWSのサービス。
    • ソーシャルログインや多要素認証にも容易に対応でき、スケーラブル。
  3. 認証フロー(図解付)

    • シーケンス図では時系列のやり取りを、システム構成図ではコンポーネントの位置関係を把握できる。
    • ユーザープールで認証し、IDプールからIAMロールを払い出してAWSリソースにアクセスする仕組み。
  4. トークン管理

    • アクセストークンはメモリ、ID・リフレッシュトークンはHttpOnly Cookieなどで安全に管理。
    • XSSやCSRFなどの攻撃対策を忘れずに実施。
  5. NextAuthとは?

    • Next.js向けの認証ライブラリ。Cognitoをはじめとする多様なプロバイダーに対応し、実装の手間を大幅に削減。
  6. NextAuthを使った実装サンプル

    • CognitoProvider を設定し、コールバック内でアクセストークンなどをJWTに反映。
    • ミドルウェアで特定パスへのアクセスを制限し、未認証ならログインページへリダイレクト。
  7. 総合まとめ

    • Cognitoによる認証・認可とNextAuth.jsの組み合わせで、セキュアかつスケーラブルな認証基盤を構築可能。
    • セキュリティに留意し、定期的なレビューや更新を行い、ユーザー体験を向上させましょう。

このように、認証フローを正しく設計・把握することが、安全なシステムを構築する第一歩です。CognitoとNextAuth.jsを活用し、最新のセキュリティプラクティスを取り入れた認証基盤をぜひ構築してみてください。

Discussion