Open10

Learn | Next.js(AppRouter) x NextAuth(v4) x Cognito x Custom Login Page

TakayyzTakayyz

Next.js(App Router)とNextAuth(v4)での実装事例をあまり見かけなかったので調べた情報を自分なりにまとめる。
※App Routerは採用するけどNextAuthのv5は現時点ではまだbeta版のためv4を使いたい、またサービスのトンマナの関係からログインページはHosted UIではなくカスタムログインページを作成したいという背景

だいたいよくある組み合わせは、

  • Next.js(Page Router) x NextAuth(v4)
    もしくは
  • Next.js(App Router) x NextAuth(v5@beta)

が多く、紹介されている記事によって(コード)が若干違ったりするのでややこしかった。

language/framework/package/etc version/detail
React ^18
Next.js 14.1.3
ルーティング機構 App Router
NextAuth ^4.24.6
HostedUIの使用 ✗(カスタムログインページ作成)

検証環境のディレクトリ構成はsrc配下にapp routerの構成。


ちなみにこういう全体の流れを俯瞰して見れるドキュメント(シーケンス図etc)が欲しかったからやっぱり公式ドキュメントを熟読するのは大切。
https://next-auth.js.org/configuration/providers/oauth

TakayyzTakayyz

TL;DR

[追記]
この記事によると、変数に格納する形でCognitoProviderも用意しておくと後続処理が少し楽になるらしい
→CognitoProviderに定義した情報を流用可能になるため

機能を実現するために最低限必用なセットアップ

  1. Cognitoセットアップ
  2. Google Cloudセットアップ※CognitoでGoogle SSOを行いたい場合
  3. NextAuthセットアップ
    a. NextAuthインストールとRoute Handlerでの初期化 Document
    b. CognitoProviderセットアップ
    c. 認証状態をアプリケーションで参照するためにsession state(provider)セットアップ Document
    d. トークンリフレッシュ処理実装
    - next-auathの型を拡張 article
    e. オプションセットアップ Document
    f. middleware作成
    g. コンポーネントでSessionProviderから提供されるセッション情報の利用 Document
TakayyzTakayyz

1. Cognitoセットアップ

事前準備

※Cognitoからメールを送信する際SESを使用する場合のみ

  1. Route53でSES用のドメイン登録(※もし使えるドメインが無い場合)
  2. SESでメアド登録
  3. SESの制限緩和申請(※商用利用等する場合)

本設定

  1. Cognitoの設定
  2. TBD
TakayyzTakayyz

3. NextAuthセットアップ

a. NextAuthインストール

ドキュメントに沿ってインストール
※パッケージマネージャは自身が使っているものを使う

bun add next-auth

b. Route Handlerセットアップ

  • TBD
  • TODO: CognitoProviderのoptionのchecks: nonceの挙動について調査
TakayyzTakayyz

動作確認

問題1

諸々設定後サーバー立ち上げてブラウザ操作しようとするとこれが発生...

 ⨯ node_modules/oidc-token-hash/lib/shake256.js (3:0) @ <unknown>
 ⨯ Cannot read properties of undefined (reading 'substring')
null

Next.jsとNextAuthのバージョン問題か...?
https://github.com/vercel/ai-chatbot/issues/133

原因

src/middleware.tsnext-authからimportしたNextAuthモジュールをインスタンス化してexportしていたのが原因の様子。(これはv5の書き方なのかな)

import NextAuth from 'next-auth';
import { authConfig } from '@/auth.config';

export default NextAuth(authConfig).auth;

↓にしたらエラーは解消

export {default} from 'next-auth/middleware';

解決の糸口はnextauth middlewareで検索してヒットしたこの記事

TakayyzTakayyz

src/middleware.tsnext-authからimportしたNextAuthモジュールをインスタンス化してexportしていたのが原因の様子。

import NextAuth from 'next-auth';
import { authConfig } from '@/auth.config';

export default NextAuth(authConfig).auth;

↓にしたらエラーは解消

export {default} from 'next-auth/middleware';

解決の糸口はnextauth middlewareで検索してヒットしたこの記事

TakayyzTakayyz

問題2

問題解消後再度ブラウザでリクエストするとERR_TOO_MANY_REDIRECTSが発生する。

原因

middlewareのmatcherの指定が悪かった。※根本的な解決ではない気がする
middlewareで認証失敗した場合auth.config.tsで定義したpagesオプションのsignIn: '/login'に遷移するが、middlewareのmatcherで/loginも対象に含めてしまっていた為、

/loginにリダイレクト → middlewareで認証処理(未認証) → /loginにリダイレクト → middleware処理...

を繰り返していた。

// NG
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.png).*)'],
};

// OK
export const config = {
  matcher: ['/((?!login|api|_next/static|_next/image|.png).*)'],
};
TakayyzTakayyz

問題3

next-authからNextAuthからexportしたsignInactions.tsで使おうとしたらsignInが関数じゃない系のエラーになった。(詳細エラー拾うの忘れた)

// src/auth.ts

import NextAuth from 'next-auth';
import { authConfig } from '@/auth.config';

export const { auth, signIn, signOut } = NextAuth(authConfig);

原因

next-auth/reactからexportしてやる必用があった。

// src/auth.ts

export { signIn, signOut } from 'next-auth/react';
TakayyzTakayyz

問題4

error: NotAuthorizedException: SecretHash does not match for the client: XXXXXXXXX

SecretHashが一致しないエラー。

原因

auth.config.tsのSecretHashを生成している箇所のロジックミス。
環境変数をうまく取得できていなかった。
COGNITO_CLIENT_SECRETCOGNITO_CLIENT_IDが自分のenvに定義してあるか確認

  const secretHash = crypt
    .createHmac('sha256', process.env.COGNITO_CLIENT_SECRET ?? '')
    .update(email + process.env.COGNITO_CLIENT_ID)
    .digest('base64');