> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js アプリケーションにログインを追加する

> このガイドでは、Auth0 Next.js v4 SDK を使用して、新規または既存の Next.js アプリケーションに Auth0 を統合する方法を説明します。

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

export const CreateInteractiveApp = ({placeholderText = "Auth0", appType = "regular_web", allowedCallbackUrls = ["localhost:3000"], allowedLogoutUrls = ["localhost:3000"], allowedOriginUrls = ["localhost:3000"]}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  const [displayForm, setDisplayForm] = useState(true);
  useEffect(() => {
    const init = () => setStoreReady(true);
    if (window.rootStore) {
      window.rootStore.clientStore.setSelectedClient(null);
      window.rootStore.clientStore.setSelectedClientSecret(undefined);
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
    };
  }, []);
  useEffect(() => {
    if (!storeReady) return;
    const disposer = autorun(() => {
      const rootStore = window.rootStore;
      setIsAuthenticated(rootStore.sessionStore.isAuthenticated);
    });
    return () => {
      disposer();
    };
  }, [storeReady]);
  if (!storeReady || typeof window === "undefined" || !displayForm) {
    return <></>;
  }
  const login = () => {
    const baseUrl = window.rootStore.config.apiBaseUrl;
    const returnTo = encodeURIComponent(window.location.href);
    window.location.href = `${baseUrl}/auth/user/login?returnTo=${returnTo}`;
  };
  const Card = ({className = "", children}) => {
    return <div className={`
          flex border rounded-2xl
          border-gray-950/10 dark:border-white/10
          py-3.5 px-4 gap-2
          text-sm text-gray-900 dark:text-gray-200
          ${className}
        `}>
        {children}
      </div>;
  };
  const Button = ({children, ...props}) => {
    return <button className="bg-[--button-primary] text-[--foreground-inverse] px-[1.125rem] py-1.5 rounded-lg font-medium" {...props}>
        {children}
      </button>;
  };
  const CreateApplicationForm = () => {
    const [name, setName] = useState("");
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState("");
    const handleSubmit = async () => {
      if (!name.trim()) {
        setError("アプリケーション名は必須です");
        return;
      }
      setIsLoading(true);
      setError(null);
      try {
        await window.rootStore.clientStore.createClient({
          name: name.trim(),
          app_type: appType,
          callbacks: allowedCallbackUrls,
          allowed_logout_urls: allowedLogoutUrls,
          web_origins: allowedOriginUrls,
          client_metadata: {
            created_by: "quickstart-docs-app-creation-component"
          }
        });
        setDisplayForm(false);
      } catch (err) {
        console.error("Error creating client:", err);
        const errorMessage = err instanceof Error ? err.message : "アプリケーションの作成に失敗しました";
        setError(errorMessage);
      } finally {
        setIsLoading(false);
      }
    };
    return <Card className="flex-col items-start p-4 gap-3.75">
        <span className="font-medium text-gray-900 dark:text-gray-200">Auth0 アプリを作成</span>
        <div className="w-full flex gap-2">
          <input id="app-name" name={name} className="
              w-full max-w-[448px] h-11 py-2 px-4 
              border rounded-lg border-gray-950/10 dark:border-white/10 
              text-gray-900 dark:text-gray-200
              focus:outline-none dark:focus:outline-none
            " placeholder={`マイ ${placeholderText} アプリ`} value={name} onChange={e => setName(e.target.value)} />
          <Button onClick={handleSubmit}>{isLoading ? "作成中…" : "作成"}</Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>ログイン</Button> <span>アプリを作成するには</span>
      </Card>;
  };
  return isAuthenticated ? <CreateApplicationForm /> : <SignInForm />;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

<HowToSchema />

<Accordion title="AI を使って Auth0 を統合する" icon="microchip-ai" iconType="solid" defaultOpen>
  Claude Code、Cursor、GitHub Copilot などの AI コーディングアシスタントを使えば、[agent skills](https://agentskills.io/home) を利用して数分で Auth0 認証を自動的に追加できます。

  **インストール:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0
  ```

  **次に、AI アシスタントに次のように依頼します:**

  ```text theme={null}
  Add Auth0 authentication to my Next.js app
  ```

  AI アシスタントが自動的に Auth0 アプリケーションを作成し、資格情報を取得し、`@auth0/nextjs-auth0` をインストールし、API ルートを作成して、環境変数を設定します。[agent skills の完全なドキュメント →](/docs/ja-jp/quickstart/agent-skills)
</Accordion>

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  **前提条件:** 始める前に、次のものがインストールされていることを確認してください。

  * **[Node.js](https://nodejs.org/en/download)** 20 LTS 以降
  * **[npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)** 10 以上、または **[yarn](https://classic.yarnpkg.com/lang/en/docs/install/)** 1.22 以上、または **[pnpm](https://pnpm.io/installation)** 8 以上

  インストールの確認: `node --version && npm --version`
</Callout>

<div id="get-started">
  ## はじめに
</div>

この Quickstart では、Next.js 16 アプリケーションに Auth0 の認証を追加する方法を紹介します。Auth0 Next.js SDK を使って、サーバーサイドレンダリング、安全なログイン機能、保護されたルートを備えたフルスタックのウェブアプリケーションを構築します。

export function generateRandomString(length) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  return Array.from({
    length
  }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

export const localEnvSnippet = `AUTH0_DOMAIN={yourDomain}
AUTH0_CLIENT_ID={yourClientId}
AUTH0_CLIENT_SECRET={yourClientSecret}
AUTH0_SECRET=${generateRandomString(32)}
APP_BASE_URL=http://localhost:3000`;

<Steps>
  <Step title="新しいプロジェクトを作成" stepNumber={1}>
    このQuickstart向けに新しいNext.jsプロジェクトを作成します

    ```shellscript theme={null}
    npx create-next-app@latest auth0-nextjs --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --yes
    ```

    プロジェクトを開く

    ```shellscript theme={null}
    cd auth0-nextjs
    ```
  </Step>

  <Step title="Auth0 Next.js SDK のインストール" stepNumber={2}>
    `shellscript npm install @auth0/nextjs-auth0 `
  </Step>

  <Step title="プロジェクトファイルを作成" stepNumber={3}>
    Auth0 連携に必要なディレクトリとファイルをすべて作成します。

    <CodeGroup>
      ```shellscript Mac/Linux theme={null}
      mkdir -p src/lib src/components && touch src/lib/auth0.ts src/proxy.ts src/components/LoginButton.tsx src/components/LogoutButton.tsx src/components/Profile.tsx
      ```

      ```powershell Windows theme={null}
      New-Item -ItemType Directory -Force -Path src/lib, src/components
      New-Item -ItemType File -Path src/lib/auth0.ts, src/proxy.ts, src/components/LoginButton.tsx, src/components/LogoutButton.tsx, src/components/Profile.tsx
      ```
    </CodeGroup>
  </Step>

  <Step title="Auth0 App をセットアップする" stepNumber={4}>
    次に、Auth0 テナントで新しいアプリを作成し、プロジェクトに環境変数を追加します。

    Auth0アプリをセットアップする方法は3つあります。Quick Setupツールを使用する (推奨) 、CLIコマンドを実行する、またはAuth0 Dashboardから手動で設定する方法です。

    <Tabs>
      <Tab title="クイックセットアップ（推奨）">
        Auth0 App を作成し、適切な設定値があらかじめ入力された `.env` ファイルをコピーします。

        <CreateInteractiveApp placeholderText="Next.js" appType="regular_web" allowedCallbackUrls={["http://localhost:3000/auth/callback"]} allowedLogoutUrls={["http://localhost:3000"]} allowedOriginUrls={["http://localhost:3000"]} />

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env.local" />
      </Tab>

      <Tab title="CLI">
        プロジェクトのルートディレクトリで次のコマンドを実行し、Auth0 アプリを作成して `.env` ファイルを生成します。

        <CodeGroup>
          ```shellscript Mac theme={null}
          # Auth0 CLI をインストールします（未インストールの場合）
          brew tap auth0/auth0-cli && brew install auth0

          # Auth0 アプリをセットアップし、.env ファイルを生成します
          auth0 qs setup --app --type regular --framework nextjs --name "My App" --port 3000
          ```

          ```powershell Windows theme={null}
          # Auth0 CLI をインストールします（未インストールの場合）
          scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
          scoop install auth0

          # Auth0 アプリをセットアップし、.env ファイルを生成します
          auth0 qs setup --app --type regular --framework nextjs --name "My App" --port 3000
          ```
        </CodeGroup>

        <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
          このコマンドでは以下が実行されます。

          1. 認証済みかどうかを確認します (必要に応じてログインを求められます)
          2. `http://localhost:3000` 用に設定された Auth0 Regular Web Application を作成します
          3. `AUTH0_DOMAIN`、`AUTH0_CLIENT_ID`、`AUTH0_CLIENT_SECRET`、`AUTH0_SECRET`、`APP_BASE_URL` を含む `.env` ファイルを生成します
        </Callout>
      </Tab>

      <Tab title="Auth0 Dashboard">
        プロジェクトのルートディレクトリに `.env.local` ファイルを作成します。

        ```shellscript .env.local theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_APP_DOMAIN
        AUTH0_CLIENT_ID=YOUR_AUTH0_APP_CLIENT_ID
        AUTH0_CLIENT_SECRET=YOUR_AUTH0_APP_CLIENT_SECRET
        AUTH0_SECRET=YOUR_LONG_RANDOM_SECRET_HERE
        APP_BASE_URL=http://localhost:3000
        ```

        **安全な AUTH0\_SECRET を生成します。**

        ```shellscript theme={null}
        openssl rand -hex 32
        ```

        出力結果をコピーし、`.env.local` の `YOUR_LONG_RANDOM_SECRET_HERE` を置き換えます。これは 64 文字の16進数文字列である必要があります。

        次に、Auth0 アプリケーションを設定します。

        1. [Auth0 Dashboard](https://manage.auth0.com/dashboard/) に移動します
        2. **アプリケーション** > **アプリケーション** > **Create Application** をクリックします
        3. ポップアップでアプリの名前を入力し、アプリの種類として `Regular Web Application` を選択して **Create** をクリックします
        4. Application Details ページの **設定** タブに切り替えます
        5. `.env.local` ファイル内の `YOUR_AUTH0_APP_DOMAIN`、`YOUR_AUTH0_APP_CLIENT_ID`、`YOUR_AUTH0_APP_CLIENT_SECRET` を、ダッシュボードの **Domain**、**Client ID**、**Client Secret** の値に置き換えます

        <Warning>
          **重要:** アプリの実行開始後に `AUTH0_SECRET` を変更した場合は、localhost のブラウザ Cookie をクリアする必要があります。以前の secret で暗号化された古いセッションクッキーが、`JWEDecryptionFailed` エラーの原因になります。
        </Warning>

        最後に、Application Details ページの **設定** タブで、次の URL を設定します。

        **Allowed Callback URLs:**

        ```
        http://localhost:3000/auth/callback
        ```

        **Allowed Logout URLs:**

        ```
        http://localhost:3000
        ```

        **Allowed Web Origins:**

        ```
        http://localhost:3000
        ```

        <Info>
          **Allowed Callback URLs** は、authentication の後にユーザーを安全にアプリケーションへ戻すための重要なセキュリティ対策です。一致する URL がないと、ログインプロセスは失敗し、ユーザーはアプリにアクセスできず Auth0 のエラーページに遷移します。

          **Allowed Logout URLs** は、サインアウト時にシームレスなユーザー体験を提供するうえで重要です。一致する URL がないと、logout 後にユーザーはアプリケーションへリダイレクトされず、汎用的な Auth0 のページに留まることになります。

          **Allowed Web Origins** は、サイレント認証に不可欠です。これが設定されていないと、ユーザーはページを更新したときや、後でアプリに戻ったときにログアウトされます。
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Auth0の設定を作成する" stepNumber={5}>
    Auth0のクライアントコードを`src/lib/auth0.ts`に追加します:

    ```typescript src/lib/auth0.ts lines theme={null}
    import { Auth0Client } from '@auth0/nextjs-auth0/server';

    export const auth0 = new Auth0Client();
    ```
  </Step>

  <Step title="プロキシを追加" stepNumber={6}>
    `src/proxy.ts` にプロキシコードを追加します:

    ```typescript src/proxy.ts expandable lines theme={null}
    import { auth0 } from "./lib/auth0";

    export async function proxy(request: Request) {
      return await auth0.middleware(request);
    }

    export const config = {
      matcher: [
        /*
         * 次で始まるパスを除くすべてのリクエストパスに一致:
         * - _next/static（静的ファイル）
         * - _next/image（画像最適化ファイル）
         * - favicon.ico、sitemap.xml、robots.txt（メタデータファイル）
         */
        "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
      ],
    };
    ```

    <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
      `src/` ディレクトリを使用しているため、`proxy.ts` ファイルは `src/` 内に作成されます。`src/` ディレクトリを使用していない場合は、代わりにプロジェクトのルートに作成してください。
    </Callout>

    <Info>
      このプロキシは、次の認証ルートを自動的にマウントします。

      * `/auth/login` - ログインルート
      * `/auth/logout` - ログアウトルート
      * `/auth/callback` - コールバックルート
      * `/auth/profile` - ユーザープロファイルルート
      * `/auth/access-token` - アクセストークンルート
      * `/auth/backchannel-logout` - バックチャネルログアウトルート
    </Info>
  </Step>

  <Step title="Login、Logout、Profile コンポーネントを作成" stepNumber={7}>
    Step 3で作成したファイルに、コンポーネントのコードを追加します:

    <AuthCodeGroup>
      ```typescript src/components/LoginButton.tsx lines theme={null}
      "use client";

      export default function LoginButton() {
        return (
          <a
            href="/auth/login"
            className="w-full text-center inline-block px-6 py-3 bg-gradient-to-b from-[#2d2d42] to-[#161620] hover:opacity-90 text-white font-medium rounded-full text-[14px] transition-opacity"
          >
            Login
          </a>
        );
      }
      ```

      ```typescript src/components/LogoutButton.tsx lines theme={null}
      "use client";

      export default function LogoutButton() {
        return (
          <a
            href="/auth/logout"
            className="w-full text-center inline-block px-6 py-3 bg-[#f0f0f0] hover:bg-gray-200 text-gray-600 font-medium rounded-full text-[14px] transition-colors"
          >
            Logout
          </a>
        );
      }
      ```

      ```typescript src/components/Profile.tsx lines theme={null}
      "use client";

      import { useUser } from "@auth0/nextjs-auth0/client";

      function getInitials(name?: string | null, email?: string | null): string {
        if (name) {
          const parts = name.trim().split(" ");
          return parts.length >= 2
            ? `${parts[0][0]}${parts[1][0]}`.toUpperCase()
            : parts[0].slice(0, 2).toUpperCase();
        }
        if (email) return email.slice(0, 2).toUpperCase();
        return "U";
      }

      export default function Profile() {
        const { user, isLoading } = useUser();

        if (isLoading) return <p className="text-xs text-gray-500">Loading...</p>;
        if (!user) return null;

        return (
          <>
            <div className="flex items-center gap-2 text-green-500 text-[13px] font-medium">
              <span className="w-5 h-5 bg-green-500 rounded-full flex items-center justify-center shrink-0">
                <svg width="10" height="8" viewBox="0 0 10 8" fill="none">
                  <path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </span>
              Successfully authenticated
            </div>
            <div className="flex items-center gap-2 bg-gray-100 rounded-full py-1.5 pl-1.5 pr-4 text-[12px] text-gray-700 max-w-full">
              <span className="w-7 h-7 bg-gradient-to-b from-[#2d2d42] to-[#161620] rounded-full flex items-center justify-center text-white text-[10px] font-semibold shrink-0">
                {getInitials(user.name, user.email)}
              </span>
              <span className="truncate">{user.email}</span>
            </div>
          </>
        );
      }
      ```
    </AuthCodeGroup>
  </Step>

  <Step title="メインページを更新する" stepNumber={8}>
    `src/app/page.tsx` を以下の内容に置き換えてください：

    ```typescript src/app/page.tsx lines theme={null}
    import { auth0 } from "@/lib/auth0";
    import LoginButton from "@/components/LoginButton";
    import LogoutButton from "@/components/LogoutButton";
    import Profile from "@/components/Profile";

    export default async function Home() {
      const session = await auth0.getSession();
      const user = session?.user;

      return (
        <main className="min-h-screen bg-[#efefef] flex flex-col items-center justify-center gap-4 px-6 py-12">
          <div className="bg-white rounded-[28px] shadow-[0_4px_32px_rgba(0,0,0,0.08)] px-12 py-14 flex flex-col items-center gap-4 w-[360px]">
            <svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg" className="mb-1">
              <g filter="url(#filter0_di)">
                <rect x="2" y="2" width="64" height="64" rx="16" fill="url(#paint0_linear)"/>
                <rect x="2.5" y="2.5" width="63" height="63" rx="15.5" stroke="#252525"/>
                <path d="M34.0002 18C25.1572 18 18 25.1669 18 34C18 42.8432 25.1672 50 34.0002 50C42.8333 50 50 42.8331 50 34C50 25.1669 42.8433 18 34.0002 18ZM43.9172 43.8971C43.9172 43.9071 43.9069 43.9072 43.9069 43.9172C43.9069 43.9172 43.8969 43.9272 43.8868 43.9272C43.144 44.65 41.9796 44.7303 41.0662 44.2585L40.0228 43.7265C36.2487 41.7792 31.7619 41.7792 27.9777 43.7265L26.9338 44.2585C26.0103 44.7303 24.8459 44.65 24.1132 43.9272C24.1132 43.9272 24.1031 43.9172 24.0931 43.9172C24.0931 43.9172 24.0828 43.9071 24.0828 43.8971C23.3601 43.1543 23.2797 41.9899 23.7515 41.0765L24.2837 40.0326C26.231 36.2585 26.231 31.7717 24.2837 27.9975L23.7515 26.9536C23.2797 26.0302 23.3601 24.8657 24.0828 24.133C24.0828 24.1229 24.0931 24.123 24.0931 24.123C24.0931 24.123 24.1031 24.1129 24.1132 24.1129C24.856 23.3902 26.0204 23.3099 26.9338 23.7817L27.9777 24.3137C31.7518 26.261 36.2386 26.261 40.0228 24.3137L41.0662 23.7817C41.9897 23.3099 43.1541 23.3902 43.8868 24.1129C43.8868 24.1129 43.8969 24.123 43.9069 24.123L43.9172 24.133C44.6399 24.8758 44.7203 26.0402 44.2485 26.9536L43.7163 27.9975C41.769 31.7717 41.769 36.2585 43.7163 40.0326L44.2485 41.0765C44.7203 41.9899 44.6499 43.1543 43.9172 43.8971Z" fill="white"/>
              </g>
              <defs>
                <filter id="filter0_di" x="0" y="0" width="68" height="68" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
                  <feFlood floodOpacity="0" result="BackgroundImageFix"/>
                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
                  <feMorphology radius="2" operator="dilate" in="SourceAlpha" result="effect1_dropShadow"/>
                  <feOffset/>
                  <feComposite in2="hardAlpha" operator="out"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 0.117647 0 0 0 0 0.129412 0 0 0 0 0.164706 0 0 0 0.12 0"/>
                  <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
                  <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
                  <feOffset dy="-1"/>
                  <feGaussianBlur stdDeviation="0.5"/>
                  <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
                  <feBlend mode="normal" in2="shape" result="effect2_innerShadow"/>
                </filter>
                <linearGradient id="paint0_linear" x1="34" y1="2" x2="34" y2="66" gradientUnits="userSpaceOnUse">
                  <stop/>
                  <stop offset="1" stopColor="#677190"/>
                </linearGradient>
              </defs>
            </svg>

            {user ? (
              <>
                <h1 className="text-[17px] font-bold text-gray-900 tracking-tight">Your account</h1>
                <div className="w-full h-px bg-gray-100" />
                <Profile />
                <LogoutButton />
              </>
            ) : (
              <>
                <h1 className="text-[17px] font-bold text-gray-900 tracking-tight">Welcome to Sample0</h1>
                <p className="text-[13px] text-gray-400 text-center leading-relaxed -mt-2">
                  Get started by logging in to your account
                </p>
                <div className="h-3" />
                <LoginButton />
              </>
            )}
          </div>

          <div className="flex items-center gap-1.5 text-[11px] text-gray-400">
            <span>Powered by</span>
            <img
              src="https://cdn.auth0.com/quantum-assets/dist/latest/logos/auth0/auth0-lockup-en-onlight.svg"
              alt="Auth0"
              className="h-3 opacity-40"
            />
          </div>
        </main>
      );
    }
    ```
  </Step>

  <Step title="Auth0Provider でレイアウトを更新する" stepNumber={9}>
    `src/app/layout.tsx` を更新し、Inter フォントを読み込んで、アプリを `Auth0Provider` でラップします:

    ```typescript src/app/layout.tsx lines theme={null}
    import { Inter } from "next/font/google";
    import type { Metadata } from "next";
    import { Auth0Provider } from "@auth0/nextjs-auth0/client";
    import "./globals.css";

    const inter = Inter({
      subsets: ["latin"],
      weight: ["300", "400", "500", "600", "700"],
    });

    export const metadata: Metadata = {
      title: "Auth0 Next.js App",
      description: "Next.js app with Auth0 authentication",
    };

    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return (
        <html lang="en">
          <body className={inter.className}>
            <Auth0Provider>{children}</Auth0Provider>
          </body>
        </html>
      );
    }
    ```

    <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
      v4 では、Auth0Provider は省略可能です。必要になるのは、サーバーでのレンダリング時に初期ユーザーを渡し、それを `useUser()` フックで利用できるようにしたい場合だけです。
    </Callout>
  </Step>

  <Step title="Tailwind CSS を設定する" stepNumber={10}>
    `src/app/globals.css` の内容を次の内容に置き換えます:

    ```css src/app/globals.css lines theme={null}
    @import "tailwindcss";
    ```
  </Step>

  <Step title="アプリを実行" stepNumber={11}>
    ```shellscript theme={null}
    npm run dev
    ```

    <Info>
      アプリは [http://localhost:3000](http://localhost:3000) で利用できます。Auth0 SDK v4 では、認証ルートが `/auth/*` に自動的にマウントされます (v3 のように `/api/auth/*` ではありません) 。

      ポート 3000 が使用中の場合は、`npm run dev -- --port 3001` を実行し、Auth0アプリのコールバックURLを `http://localhost:3001` に更新してください
    </Info>
  </Step>
</Steps>

<Check>
  **確認**

  これで、[localhost](http://localhost:3000/) 上で Auth0 のログインページが正常に動作するようになっているはずです
</Check>

***

<div id="troubleshooting">
  ## トラブルシューティング
</div>

<Accordion title="JWEDecryptionFailed エラー">
  `JWEDecryptionFailed: decryption operation failed` エラーが表示される場合は、`AUTH0_SECRET` が無効であるか、別のシークレットで暗号化された古いセッションクッキーが原因です。

  **解決方法:**

  1. 次のコマンドで新しいシークレットを生成します。

  ```shellscript theme={null}
  openssl rand -hex 32
  ```

  2. `.env.local` ファイルを更新します。

  ```
  AUTH0_SECRET=<your-new-64-character-hex-string>
  ```

  3. `localhost:3000` の**ブラウザクッキーを削除**します。
     * Chrome/Edge: `F12` キーを押す → Application タブ → Cookies → localhost のクッキーをすべて削除
     * Firefox: `F12` キーを押す → Storage タブ → Cookies → localhost のクッキーをすべて削除
     * Safari: Develop メニュー → Show Web Inspector → Storage タブ → Cookies → すべて削除

  4. 開発サーバーを再起動します。

  ```shellscript theme={null}
  npm run dev
  ```

  シークレットは必ず 32 バイト (16 進数 64 文字) ちょうどである必要があります。このエラーは、アプリが別のシークレットで暗号化された既存のセッションクッキーを復号しようとしたときに発生します。
</Accordion>

<Accordion title="/auth/login での 404 エラー">
  ログインをクリックすると 404 ページが表示される場合は、次のよくある原因を確認してください。

  1. **プロキシの場所:** `src/proxy.ts` が正しい場所にあることを確認します
  2. **プロキシのコード:** プロキシがステップ 6 のコードと一致していることを確認します
  3. **サーバーの再起動:** プロキシファイルを作成したら、開発サーバーを再起動します
  4. **インポートの確認:** `import { auth0 } from "./lib/auth0"` のパスが正しいことを確認します
</Accordion>

<Accordion title="Module Not Found エラー">
  "Cannot find module '@/components/LoginButton'" のようなエラーが表示される場合:

  1. **ファイルの存在を確認:** ステップ 3 のファイルがすべて作成されていることを確認します
  2. **パスの確認:** コンポーネントが `src/components/` ディレクトリ内にあることを確認します
  3. **TypeScript を再起動:** `Cmd+Shift+P` (Mac) または `Ctrl+Shift+P` (Windows) を押し、"TypeScript: Restart TS Server" を実行します
  4. **インポートの確認:** `@/components/*` を使用していることを確認します (`~/components/*` ではありません)
</Accordion>

***

<div id="advanced-usage">
  ## 高度な使い方
</div>

<Accordion title="v4 の重要な変更点">
  この Quickstart では **Auth0 Next.js SDK v4** を使用しており、v3 から大きく変更されています。

  * **動的ルートハンドラーは不要** - 認証ルートはプロキシによって自動的にマウントされます
  * **クライアント設定を簡略化** - `new Auth0Client()` は環境変数を自動的に読み込みます
  * **新しいルートパス** - ルートは `/api/auth/*` ではなく `/auth/*` にあります
  * **プロキシが必須** - すべての認証機能は `proxy.ts` を経由します
  * **`<a>` タグを使用** - ナビゲーションには、onClick 付きのボタンではなく `<a href="/auth/login">` を使用する必要があります

  ### 認証ルート

  SDK は、プロキシ経由で以下のルートを自動的にマウントします。

  | Route                      | 目的                     |
  | -------------------------- | ---------------------- |
  | `/auth/login`              | login を開始              |
  | `/auth/logout`             | ユーザーをログアウト             |
  | `/auth/callback`           | Auth0 callback を処理     |
  | `/auth/profile`            | ユーザープロファイルを取得          |
  | `/auth/access-token`       | アクセストークンを取得            |
  | `/auth/backchannel-logout` | backchannel logout を処理 |

  <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
    これらのルートで 404 エラーが発生する場合は、次の点を確認してください。

    1. `proxy.ts` ファイルが正しい場所にあること (プロジェクトルート、または `src/` ディレクトリを使用している場合は `src/` 内)
    2. Step 6 に示されている matcher パターンでプロキシが正しく設定されていること
    3. プロキシファイルの作成後に開発サーバーを再起動していること
  </Callout>
</Accordion>

<Accordion title="サーバーサイド認証">
  Auth0 Next.js SDK v4 は、App Router と Pages Router の両方のパターンをサポートしています。以下は、一般的なサーバーサイドパターンの例です。

  <Tabs>
    <Tab title="App Router - Server Component">
      ```typescript app/protected/page.tsx theme={null}
      import { auth0 } from "@/lib/auth0";
      import { redirect } from "next/navigation";

      export default async function ProtectedPage() {
        const session = await auth0.getSession();

        if (!session) {
          redirect('/auth/login');
        }

        return (
          <div>
            <h1>Protected Content</h1>
            <p>Welcome, {session.user.name}!</p>
          </div>
        );
      }
      ```
    </Tab>

    <Tab title="App Router - API Route">
      ```typescript app/api/protected/route.ts theme={null}
      import { auth0 } from "@/lib/auth0";
      import { NextResponse } from "next/server";

      export async function GET() {
        const session = await auth0.getSession();

        if (!session) {
          return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }

        return NextResponse.json({
          message: "This is a protected API route",
          user: session.user
        });
      }
      ```
    </Tab>

    <Tab title="Pages Router - Page">
      ```typescript pages/protected.tsx theme={null}
      import { auth0 } from "@/lib/auth0";
      import { GetServerSideProps } from "next";

      export default function ProtectedPage({ user }: { user: any }) {
        return (
          <div>
            <h1>Protected Content</h1>
            <p>Welcome, {user.name}!</p>
          </div>
        );
      }

      export const getServerSideProps: GetServerSideProps = auth0.withPageAuthRequired();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="クライアントサイド認証">
  クライアントサイドの認証 state には、`useUser` フックを使用します。

  ```typescript components/UserProfile.tsx theme={null}
  "use client";

  import { useUser } from "@auth0/nextjs-auth0/client";

  export default function UserProfile() {
    const { user, error, isLoading } = useUser();

    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;
    if (!user) return <div>Not logged in</div>;

    return (
      <div>
        <h2>{user.name}</h2>
        <p>{user.email}</p>
        <img src={user.picture} alt="Profile" referrerPolicy="no-referrer" />
      </div>
    );
  }
  ```
</Accordion>

<Accordion title="API ルートの保護">
  API ルートを保護するには、`withApiAuthRequired` メソッドを使用します。

  ```typescript app/api/protected/route.ts theme={null}
  import { auth0 } from "@/lib/auth0";

  export const GET = auth0.withApiAuthRequired(async function handler() {
    const session = await auth0.getSession();

    return Response.json({
      message: "This is a protected API route",
      user: session?.user
    });
  });
  ```
</Accordion>

<Accordion title="サードパーティのバックエンドで Auth0 トークンを使用する">
  Auth0 の認証トークンを必要とするサードパーティのバックエンドサービス (Convex、Supabase、Firebase など) を使用している場合は、Next.js アプリからバックエンドクライアントにアクセストークンを渡す必要があります。

  ### アクセストークンを取得する

  **サーバーサイド (App Router) :**

  ```typescript app/api/token/route.ts theme={null}
  import { auth0 } from "@/lib/auth0";
  import { NextResponse } from "next/server";

  export async function GET() {
    const session = await auth0.getSession();

    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const accessToken = session.accessToken;
    return NextResponse.json({ accessToken });
  }
  ```

  **クライアントサイド:**

  ```typescript lib/convex-client.ts theme={null}
  "use client";

  import { ConvexProviderWithAuth0 } from "convex/react-auth0";
  import { ConvexReactClient } from "convex/react";

  const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

  export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
    return (
      <ConvexProviderWithAuth0
        client={convex}
        useAuth0={() => ({
          // Next.js の API ルートからアクセストークンを取得
          getAccessToken: async () => {
            const response = await fetch("/api/token");
            const { accessToken } = await response.json();
            return accessToken;
          },
        })}
      >
        {children}
      </ConvexProviderWithAuth0>
    );
  }
  ```

  ### バックエンドを設定する

  多くのサードパーティサービスでは、トークンを検証するために Auth0 ドメイン と audience が必要です。バックエンドの設定では、次のようにします。

  ```typescript convex/auth.config.ts theme={null}
  export default {
    providers: [
      {
        domain: process.env.AUTH0_DOMAIN,
        applicationID: process.env.AUTH0_CLIENT_ID,
      },
    ],
  };
  ```

  <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
    バックエンドで必要な場合は、Auth0 アプリケーションに API audience が設定されていることを確認してください。これは Auth0 Dashboard の アプリケーション → APIs で設定するか、`.env.local` に `AUTH0_AUDIENCE` を追加し、それに合わせて SDK を設定できます。
  </Callout>

  ### トークンに関する問題のトラブルシューティング

  バックエンドで `ctx.auth.getUserIdentity()` が `null` を返す場合:

  1. **トークンが渡されていることを確認する:** ブラウザーの DevTools の Network タブで、リクエストにトークンが含まれていることを確認します
  2. **トークンの形式を確認する:** `idToken` ではなく `accessToken` を渡していることを確認します
  3. **バックエンドの設定を確認する:** バックエンドに正しい Auth0 ドメイン と クライアント ID が設定されていることを確認します
  4. **audience を確認する:** Auth0 API を使用している場合は、`AUTH0_AUDIENCE` が設定されており、API identifier と一致していることを確認します
  5. **トークンのクレームを確認する:** [jwt.io](https://jwt.io) で JWT をデコードし、想定どおりのクレームが含まれていることを確認します
</Accordion>
