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

# Cookieを使用してシングルページアプリを認証する

> バックエンドサーバーを使用して、Cookieを使ってシングルページアプリ（SPA）を認証する方法について説明します。

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 codeExample = `ISSUER_BASE_URL="{yourDomain}"
CLIENT_ID="{yourClientId}"
BASE_URL="http://localhost:3000"
APP_SESSION_SECRET="<YOUR SECRET VALUE>"`;

シングルページアプリ（SPA）のセキュリティ保護は困難な場合があります。ただし、SPAが次の基準を満たしている場合は、Cookieを使用して認証することで実装を簡素化できます。

* 独自のバックエンドを使用してクライアントに提供される。
* バックエンドと同じドメインを持つ。
* バックエンドへの認証を必要とするAPI呼び出しを行う。

このアプローチの説明と、[Node.js](https://nodejs.org/en/)を使用したサンプル実装を次に示します。

## 仕組み

以下の手順では、トークンを取得して使用する方法を示します。このアプローチでは、従来の[Proof Key for Code Exchangeを使用した認可コードフロー](/docs/ja-jp/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce)の代わりに、[フォームPOSTを使用した暗黙フロー](/docs/ja-jp/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post)が使用されます。これは、アクセスを要求しているのが独自のリソースである場合、フォームPOST応答モードの方がログインを実装する簡単な方法であるためです。

<Frame>
  <img src="https://mintcdn.com/docs-dev-fix-docs-5525/nyn7PO6A36QuMyn8/docs/images/ja-jp/cdy7uua7fh8z/7suPpG6SuHuCOFHeFmE21T/5267823b7316b17b35b5bdea60ddc03d/2023-09-22_13-21-34__ja-JP_.png?fit=max&auto=format&n=nyn7PO6A36QuMyn8&q=85&s=dc89948584c01f96d2734c3f89605392" alt="Authenticate Single-Page Apps Using Cookies How it Works part 1 diagram" width="430" height="379" data-path="docs/images/ja-jp/cdy7uua7fh8z/7suPpG6SuHuCOFHeFmE21T/5267823b7316b17b35b5bdea60ddc03d/2023-09-22_13-21-34__ja-JP_.png" />
</Frame>

1. ユーザーがブラウザを使用して保護されたルートにアクセスするか、認証手順の開始を必要とする何らかのアクションを実行します（ログインボタンをクリックするなど）
2. ブラウザクライアントは、ユーザーの操作内容に応じて、バックエンドの`/login`ルートまたは保護されたルートにリダイレクトします
3. バックエンドは、認可サーバーの`/authorize`エンドポイントへの要求を作成し、ブラウザクライアントをそこにリダイレクトします
4. ユーザーは、認可サーバーが提示する任意の方法を使用して認証するように求められます
5. 認可サーバーは、トークンをURLエンコードされたフォームPOSTとしてリダイレクトURIにPOSTします。バックエンドは、本文データを解析してこれらのトークンを取得できます。

この時点で、ユーザーは認証され、バックエンドは必要なトークンを持っています。これで、クライアントでこの状態を表すCookieを作成できます。次に、クライアントブラウザは、SPAを提供するルートにリダイレクトされ、認証Cookieも受け取ります。

今後、AJAX呼び出しを使用してAPI呼び出しが行われると、このCookieはクライアントとバックエンドの間で交換されます。要求ごとに、バックエンドはCookieがまだ有効かどうかを確認し、有効であれば要求続行を許可します。

<Frame>
  <img src="https://mintcdn.com/docs-dev-fix-docs-5525/KNeNQVuuteDqnTi5/docs/images/ja-jp/cdy7uua7fh8z/6SCp9KwQkwJV7L26TOuxIO/518d563a979195af59e0c5638b49eadb/2023-09-22_13-21-50.png?fit=max&auto=format&n=KNeNQVuuteDqnTi5&q=85&s=505249d0f833955e02070dc64c883689" alt="Authenticate Single-Page Apps Using Cookies How it Works part 2 diagram" width="747" height="346" data-path="docs/images/ja-jp/cdy7uua7fh8z/6SCp9KwQkwJV7L26TOuxIO/518d563a979195af59e0c5638b49eadb/2023-09-22_13-21-50.png" />
</Frame>

### 無効または欠落しているCookie

このアプローチを実装する場合、認証Cookieが無効または欠落している場合の処理が必要になります。クライアントからバックエンドへのAPI呼び出しはバックグラウンドで行われるため、クライアントは、ユーザーが再認証する必要があることを示すサーバーからの応答を処理する必要があります。

次のサンプルアプリケーションでは、API呼び出しの結果が302リダイレクトである場合にユーザーに再認証を求めるという単純な方法でこのケースが処理されます。302が発生するのは、Cookieの検証に失敗すると、サーバーが認可サーバーの認可エンドポイントにリダイレクトしようとし、この応答をクライアントに送信するためです。

## 例

サンプルアプリケーションでは、Node.jsとExpressを使用して、上記の概念を説明します。

### 前提条件

* この手順を実行するには、[最新バージョンのNode](https://nodejs.org/en/download/)がインストールされていることを確認してください。

* Nodeがインストールされたら、[ソースコードをダウンロードまたは複製](https://github.com/auth0-blog/spa-cookie-demo/)し、ターミナルウィンドウ内でプロジェクトフォルダーを開きます。

  ```shell lines theme={null}
  # Clone the tutorial respository using SSH
  git clone git@github.com:auth0-blog/spa-cookie-demo
  # ... or if you use HTTPS:
  git clone https://github.com/auth0-blog/spa-cookie-demo.git
  # Move into the project directory
  cd spa-cookie-demo
  ```

* `master`ブランチは、認証が追加される前のアプリケーションの状態を表します。アプリケーションの最終バージョンを参照する場合は、`with-oidc`ブランチをチェックアウトします。
  `git checkout with-oidc`

### Node.jsアプリを初期化する

ターミナルウィンドウから`npm install`を実行して、アプリケーションの依存関係をインストールします。アプリケーションを実行するには、`npm run dev`を使用します。これにより、Expressサーバーが起動します。アプリケーションを表示するには、ブラウザで[http://localhost:3000](http://localhost:3000/)にアクセスしてください。

開発サーバーは`nodemon`を使用します。nodemonはファイルの変更を検出すると自動的に再起動します。

### アプリケーションを探索する

アプリケーションを[http://localhost:3000](http://localhost:3000/)で開いた状態で、**［Call API（APIの呼び出し）］** ボタンをクリックします。画面にメッセージが表示されます。

<Frame>
  <img src="https://mintcdn.com/docs-dev-fix-docs-5525/Kb8P46QU77TFjfuu/docs/images/ja-jp/cdy7uua7fh8z/7JhdgzFLig2xcDle7ziiGp/c1db6272d20d4a291ba90a6968460109/image3.png?fit=max&auto=format&n=Kb8P46QU77TFjfuu&q=85&s=2b0fc9b69ca52cfb094d1a0fe219965e" alt="SPA Authentication with Cookies Explore the Application screen" width="937" height="677" data-path="docs/images/ja-jp/cdy7uua7fh8z/7JhdgzFLig2xcDle7ziiGp/c1db6272d20d4a291ba90a6968460109/image3.png" />
</Frame>

ログインせずにAPI呼び出しを実行できました。API呼び出しを実行する前に、ユーザーの認証を要求するミドルウェアを追加してこれを修正しましょう。

### 環境をセットアップする

アプリケーションが認証を使用して動作するには、`express-openid-connect`にいくつかの環境変数が必要です。このアプリケーションでは、これらの変数は`.env`ファイルで指定できます。プロジェクトディレクトリのルートに`.env`ファイルを作成し、次の内容を入力します。

```bash lines theme={null}
ISSUER_BASE_URL=<YOUR OIDC URL>
CLIENT_ID=<YOUR OIDC CLIENT ID>
BASE_URL=http://localhost:3000
APP_SESSION_SECRET=<YOUR SECRET VALUE>
```

### Auth0でアプリをセットアップする

1. [［Dashboard］>［Applications（アプリケーション）］>［Applications（アプリケーション）］](https://manage.auth0.com/#/applications)に移動して、**［Create Application（アプリケーションの作成）］** をクリックします。
2. 新しいアプリケーションに名前を付け、**［Regular Web Applications（通常のWebアプリケーション）］** を選択して、**［Create（作成）］** をクリックします。
3. 新しいアプリの **［Settings（設定）］** で、`http:/localhost:3000/callback`を **［Allowed Callback URLs（許可されているコールバックURL）］** に追加します。
4. `http:/localhost:3000`を **［Allowed Logout URLs（許可されているログアウトURL）］** に追加します。
5. **［Save Changes（変更を保存）］** をクリックします。
6. [［Dashboard］>［Authentication（認証）］>［Social（ソーシャル）］](https://manage.auth0.com/#/connections/social)に移動し、ソーシャル接続をいくつかセットアップします。**［Connections（接続）］** タブの **［Application（アプリケーション）］** オプションで、アプリに対してそれらを有効にします。例では、ユーザー名/パスワードデータベース、Facebook、Google、およびXを使用します。
7. **［Settings（設定）］** 画面で、上部のドメインとクライアントID設定をメモします。
8. アプリケーションの一部として構成する必要がある値が2つあります。`.env`ファイルを再度開き、次の値を設定します。

   <AuthCodeBlock children={codeExample} language="shell" lines />

### アプリを実行する

1. サーバーと環境の構成が完了したら、アプリケーションが開いているブラウザーウィンドウを見つけます。ブラウザーを閉じてサーバーを停止した場合は、ターミナルから次のコマンドを実行してアプリケーションを再起動します。
   `npm run dev`
2. ブラウザーで[http://localhost:3000](http://localhost:3000)を開きます。ユーザーインターフェイス上のアプリは同じように見えますが、今回は、**［Call API（APIの呼び出し）］** ボタンをクリックするとユーザーがログインしていないという警告が表示されます。また、APIへの呼び出しが拒否されたため、以前のように「Hello, World」メッセージが表示されることもありません。
3. **［Log in now（今すぐログイン）］** をクリックしてログインします。認証されると、アプリに戻り、新しいログイン状態を反映した更新されたUIが表示されます。**［Call API（APIの呼び出し）］** ボタンをもう一度押すと、サーバーへのAPI呼び出しが実行され、今度は成功します。
4. ページ上部の **［Profile（プロファイル）］** リンクをクリックすると、IDトークンから取得したユーザー情報が表示されます。

## もっと詳しく

* [フォームPOSTを使った暗黙フロー](/docs/ja-jp/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post)
* [セキュリティ保護](/docs/ja-jp/secure)
* [sameSiteクッキー属性の変更](/docs/ja-jp/manage-users/cookies/samesite-cookie-attribute-changes)
