nextjs-auth0のヘルパーメソッドを使ってログイン処理をスキップする

Next.js × Auth0でアプリを開発しています。
Next.jsではServer Componentを採用しており、Playwrightでコンポーネント統合テスト[^1]を実施しています。
しかし毎回ログイン画面に遷移して、IDとパスワードを入力するのは時間も手間もかかって大変でした。

そこで、nextjs-auth0のヘルパーメソッドを使って、**ログイン状態をスキップできる**方法を試してみることにしました。

[^1]:コンポーネント間のインターフェース、および相互処理に焦点を当てる。コンポーネント統合テストは、ボトムアップ、トップダウン、ビックバンといった統合戦略のアプローチに大きく依存する（ISTQBテスト技術者資格制度
Foundation Level シラバス 日本語版 Version 2023V4.0.J02 2.2.1 テストレベル）

## nextjs-auth0のヘルパーでログイン状態を作る

nextjs-auth0にはテスト用のユーティリティとして、`generateSessionCookie`というメソッドが提供されています。  

https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#generatesessioncookie

これは、[Auth0の認証フロー](https://auth0.com/docs/ja-jp/get-started/authentication-and-authorization-flow/authorization-code-flow)にて、IDトークンとアクセストークンを**疑似的**に再現することで、ログイン後のセッション情報を作成できます。

```mermaid

sequenceDiagram
  actor user as ユーザー
  participant web as Webアプリ
  participant tenant as Auth0テナント
  participant api as 自身のAPI

  user ->> web : ① ログインをクリック
  web ->> tenant : ② 認可コード要求を/authorizeに送信
  tenant ->> user : ③ ログイン/認可プロンプトにリダイレクト
  user ->> tenant : ④ 認証と同意
  tenant ->> web : ⑤ 認可コード
  web ->> tenant : ⑥ アプリケーション資格情報の認可コード
  tenant ->> tenant : ⑦ 認可コードとアプリケーション資格情報を検証
  tenant ->> web : ⑧ IDトークンとアクセストークン
  web ->> api : ⑨ アクセストークンでユーザーデータを要求
  api ->> web : ⑩ 応答
```

※ [Auth0の認証フロー](https://auth0.com/docs/ja-jp/get-started/authentication-and-authorization-flow/authorization-code-flow)をもとに作成

このセッションCookieを用意することで、ログイン状態が必要なページ（middleware.tsなどで保護されているルート）に対しても、ログイン済みとしてアクセスできるようになります。

```middleware.ts
import { NextRequest, NextResponse } from "next/server"
import { auth0 } from "@/lib/auth0"

export async function middleware(request: NextRequest) {
  const authRes = await auth0.middleware(request)

  if (request.nextUrl.pathname.startsWith("/auth")) {
    return authRes
  }

  // ここでログイン状態かを確認している
  const session = await auth0.getSession(request)
  if (!session) {
    // user is not authenticated, redirect to login page
    return NextResponse.redirect(new URL("/auth/login", request.nextUrl.origin))
  }

  // the headers from the auth middleware should always be returned
  return authRes
}

```

※ nextjs-auth0の[Examples.mdのAccessing the authenticated user](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#accessing-the-authenticated-user)のサンプルを記載

## 実装例

### 開発環境

- TypeScript : 5.8.3
- Next.js 15.2.4
- playwright/test 1.51.0 
- nextjs-auth0 4.4.1

:::note warn
nextjs-auth0 4.4.1を活用しています。
最新版（4.4.2）では不具合[^2]が発生しており、動作していません。
:::

[^2]:https://github.com/auth0/nextjs-auth0/issues/1945

### サンプルコード

Playwrightのfixtureにて、sessionを疑似生成し、playwrightでcookieに追加[^3]します。

[^3]:https://playwright.dev/python/docs/next/api/class-browsercontext#browser-context-add-cookies

```login.ts

import { test as base, Page } from "@playwright/test";

export const test = base.extend<{ page: Page }>({
  page: async ({ browser }, use) => {
    const context = await browser.newContext();

    // MEMO: ESModuleをサポートしていないため、Auth0のテスト用モジュールを動的にインポート
    const { generateSessionCookie } = await import("@auth0/nextjs-auth0/testing");
    const sessionCookieValue = await generateSessionCookie(
      {
        user: {
          sub: `auth0|${Math.floor(Math.random() * 1000000).toString()}`,
          name: "Sample",
          email: "sample@example.com",
        },
        tokenSet: {
          accessToken: Math.floor(Math.random() * 1000000).toString(),
          expiresAt: Math.floor(Date.now() / 1000) + 3600,
        },
      },
      {
        secret: process.env.AUTH0_SECRET!,
      },
    );

    // 疑似作成したセッションをCookieに追加
    await context.addCookies([
      {
        name: "appSession",
        value: sessionCookieValue,
        domain: "localhost",
        path: "/",
        httpOnly: true,
        secure: false,e
        sameSite: "Lax",
      },
    ]);
    const page = await context.newPage();

    await use(page);

    await context.close();
  },
});

export { expect } from '@playwright/test';

```

あとは作成したfixtureをテストで利用します。

```sample.spec.ts

import { test, expect } from './login';

test('sample test', async ({ page }) => {
  await page.goto("/user"); // ログイン処理を飛ばしている
  await expect(page.getByTestId('title')).toContainText("Sample Title);
});

```

この方法により、毎回IDとパスワードを入力せずにテストできるようになり、本来確認したい処理や画面遷移のテストに集中できます。

## 参考情報

- [nextjs-auth0](https://github.com/auth0/nextjs-auth0/tree/main)
- [playwright](https://playwright.dev/)
- [Auth0の認可コードフロー](https://auth0.com/docs/ja-jp/get-started/authentication-and-authorization-flow/authorization-code-flow)