> ## 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.

# APIとSPAの構成（SPA + API）

> SPA + APIアーキテクチャシナリオでのAPIと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>;
};

このセクションでは、当社シナリオでAPIを実装する方法を説明します。

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  分かりやすくするために、実装では認証と認可に焦点をあてています。サンプルからも分かるとおり、入力のタイムシートエントリーはハードコードされるため、APIはタイムシートエントリーを保持しません。代わりに、情報の一部をエコーバックします。
</Callout>

## APIエンドポイントの定義

まず、APIのエンドポイントを定義する必要があります。

<Card title="APIエンドポイントとは">
  **APIエンドポイントは**

  たとえば、レストランAPIには`/orders`や`/customers`などのエンドポイントがあるかもしれません。このAPIに接続するアプリケーションは、関連するHTTPメソッド（`POST`、`GET`、`PUT`、`PATCH`、`DELETE`）を使ってAPIエンドポイントを呼び出すことにより、CRUD操作（作成、読み取り、更新、削除）を実行することができます。
</Card>

この実装では、2つのエンドポイントのみ定義します。1つは従業員の全タイムシートのリストを取得するため、もう1つは従業員がタイムシートエントリーを新規作成できるようにするためのものです。

`/timesheets`エンドポイントへの`HTTP GET`要求は、ユーザーがタイムシートを取得できるようにし、`/timesheets`への`HTTP POST`要求は、ユーザーが新たなタイムシートを追加できるようにします。

[**Node.js** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/api-implementation-nodejs#1-define-the-api-endpoints)**での実装を参照してください** 。

### エンドポイントのセキュリティ確保

APIがヘッダーの一部にBearerアクセストークンのある要求を受け取った場合、最初にすべきことはトークンの検証です。これは複数の手順で構成され、そのうち1つでも失敗した場合、要求は、`Missing or invalid token`という呼び出し元アプリへのエラーメッセージとともに拒否されなければなりません。

APIは以下の検証を実行すべきです。

* <Tooltip data-tooltip-id="react-containers-DefinitionTooltip-3" href="/docs/ja-jp/glossary?term=json-web-token" tip="JSON Web Token（JWT）: 二者間のクレームを安全に表現するために使用される標準IDトークン形式（および多くの場合、アクセストークン形式）。" cta="用語集の表示">JWT</Tooltip>が整形式であることを確認する
* 署名を確認する
* 標準クレームを検証する

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  [JWT.io](https://jwt.io/)は、JWTの解析から署名・クレームの検証まで、ほとんどの作業に役立つライブラリーのリストを提供します。
</Callout>

検証プロセスにはアプリケーション権限（スコープ）の確認も含まれますが、これに関しては次の段落で別に説明します。

アクセストークン検証の詳細については、[「アクセストークンを検証する」](https://auth0.com/docs/tokens/guides/validate-access-tokens)を参照してください。

[**Node.js** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/api-implementation-nodejs#2-secure-the-api-endpoints)**での実装を参照してください** 。

### アプリケーション権限の確認

この段階ではJWTの有効性が検証されています。最後の手順は、アプリケーションが保護されたリソースにアクセスするために必要な権限を持っているかどうかを検証することです。

そのためには、APIがデコードされたJWTの[スコープ](https://auth0.com/docs/scopes)を確認する必要があります。このクレームはペイロードの一部で、スペースで区切られた文字列のリストです。

[**Node.js** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/api-implementation-nodejs#3-check-the-client-permissions)**での実装を参照してください** 。

### ユーザーIDの判断

どちらのエンドポイント（タイムシートのリスト取得用と新規タイムシート追加用）でも、ユーザーのIDを判断する必要があります。

これは、タイムシートのリスト取得に関しては、要求元のユーザーのタイムシートのみを返すようにするためです。一方の新規タイムシートの追加に関しては、タイムシートがその要求元のユーザーと関連付けられていることを確認するためです。

標準JWTクレームの1つは、クレームの対象である本人を識別する`sub`クレームです。暗黙的付与フローの場合、このクレームにはAuth0ユーザーの一意の識別子であるIDが含まれます。これを使って、外部システムのいかなる情報でも、特定ユーザーに関連付けることができます。

また、カスタムクレームを使って、メールアドレスなど別のユーザー属性をアクセストークンに追加し、ユーザーを一意に識別することもできます。

[**Node.js** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/api-implementation-nodejs#4-determine-the-user-identity)**での実装を参照してください** 。

## SPAの実装

このセクションでは、当社シナリオでSPAを実装する方法を説明します。

### ユーザーの認証

ユーザーの認証には[auth0.jsライブラリ](https://auth0.com/docs/libraries/auth0js)が使われます。Auth0アプリケーションの新しいインスタンスを次のように初期化できます。

export const codeExample = `var auth0 = new auth0.WebAuth({
  clientID: '{yourClientId}',
  domain: '{yourDomain}',
  responseType: 'token id_token',
  audience: 'YOUR_API_IDENTIFIER',
  redirectUri: '{https://yourApp/callback}',
  scope: 'openid profile read:timesheets create:timesheets'
});`;

<AuthCodeBlock children={codeExample} language="javascript" />

以下の構成値を渡す必要があります。

* **clientID** ：Auth0のクライアントIdの値。これは[Dashboard](https://manage.auth0.com/#/applications%7D)にある［Application（アプリケーション）］の［Settings（設定）］で取得できます。
* **domain** ：Auth0ドメインの値。これは[Dashboard](https://manage.auth0.com/#/applications%7D)にある［Application（アプリケーション）］の［Settings（設定）］で取得できます。
* **responseType** ：使用する認証フローです。**暗黙フロー** を使うSPAの場合は、`token id_token`に設定します。`token`の部分はURLフラグメントでアクセストークンを返すフローをトリガーし、`id_token`の部分はIDトークンも返すフローをトリガーします。
* **<Tooltip data-tooltip-id="react-containers-DefinitionTooltip-6" href="/docs/ja-jp/glossary?term=audience" tip="オーディエンス: 発行されたトークンに対するオーディエンスを表す一意の識別子。トークンでaudという名前が付けられ、その値にはIDトークンの場合はアプリケーション（Client ID）、アクセストークンの場合はAPI（API Identifier）のいずれかのIDが含まれます。" cta="用語集の表示">audience</Tooltip>** ：API識別子の値。これはDashboardにある[［Settings of your API（APIの設定）］](https://manage.auth0.com/#/apis%7D)で取得できます。
* **redirectUri** ：ユーザー認証後のAuth0のリダイレクト先URL。
* **scope** ：IDトークンとアクセストークンで返される情報を決定する[スコープ](https://auth0.com/docs/scopes)。`openid profile`のスコープは、IDトークン中のユーザープロファイル情報をすべて返します。APIを呼び出すために必要なスコープも要求する必要があります。この場合は、`read:timesheets create:timesheets`スコープです。そうすることで、アクセストークンにこれらのスコープがあるようにします。

認証フローを開始するには、`authorize()`メソッドを呼び出すことができます。

```javascript lines theme={null}
auth0.authorize();
```

Auth0は認証後、Auth0アプリケーションの新たなインスタンス構成時に指定した**redirectUri** に再びリダイレクトして戻ります。この時点で、URLハッシュフラグメントを解析する`parseHash()`メソッドを呼び出してAuth0認証応答の結果を抽出する必要があります。

parseHashが返すauthResultオブジェクトの内容は、どの認証パラメーターが使われたかによって異なります。以下を含む可能性があります。

* **idToken** ：ユーザープロファイル情報が入ったIDトークンJWT
* **accessToken** ：**audience** で指定されたAPIのアクセストークン
* **expiresIn** ：アクセストークンの有効期限（秒数）を含む文字列

[トークンを保管する](https://auth0.com/docs/tokens/concepts/token-storage)ための最適な場所を決定します。シングルページアプリ（SPA）に1つでもバックエンドサーバーがあれば、トークンは[認可コードフロー](https://auth0.com/docs/flows/concepts/auth-code)または[Proof Key for Code Exchange（PKCE）を使った認可コードフロー](https://auth0.com/docs/flows/concepts/auth-code-pkce)を使ってサーバー側で処理します。

対応するバックエンドサーバーがないSPAの場合、ログイン時に新しいトークンを要求し、それらをメモリ内に保存しますが、永続的に保存しないようにします。APIの呼び出しをするには、SPAがトークンのインメモリコピーを使用します。

SPAにおけるセッションの処理方法については、[「JavaScriptシングルページアプリのQuickstart」](https://auth0.com/docs/quickstart/spa/vanillajs)で、[「認可トークンの処理」](https://auth0.com/docs/quickstart/spa/vanillajs#handle-authentication-tokens)セクションに記載されている例を参照してください。

[**Angular 2** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/spa-implementation-angular2#2-authorize-the-user)**での実装を参照してください** 。

### ユーザープロファイルの取得

<Card title="トークンから情報を抽出する">
  このセクションでは、アクセストークンと[/userinfoエンドポイント](https://auth0.com/docs/api/authentication#get-user-info)を使って、ユーザー情報を取得する方法について説明します。APIの呼び出しを避けたい場合には、[ライブラリーを使って](https://jwt.io/#libraries-io)、単にIDトークンをデコードすることもできます（必ず先に検証をしてください）。他のユーザー情報が追加で必要な場合は、バックエンドからの[Management API](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id)の使用を検討してください。
</Card>

ユーザーのプロファイル情報を取得するために、返された`authResult.accessToken`を渡して`client.userInfo`メソッドを呼び出すことができます。その場合、[/userinfoエンドポイント](https://auth0.com/docs/api/authentication#get-user-info)に要求が送られ、以下の例によく似た、ユーザー情報が入った`user`オブジェクトが返されます。

```json lines theme={null}
{
    "email_verified": "false",
    "email": "test@example.com",
    "clientID": "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH",
    "updated_at": "2017-02-07T20:50:33.563Z",
    "name": "tester9@example.com",
    "picture": "https://gravatar.com/avatar/example.png",
    "user_id": "auth0|123456789012345678901234",
    "nickname": "tester9",
    "created_at": "2017-01-20T20:06:05.008Z",
    "sub": "auth0|123456789012345678901234"
}
```

これらのプロパティには`userInfo`関数を呼び出す際に渡されたコールバック関数でアクセスできます。

```javascript lines theme={null}
const accessToken = authResult.accessToken;

auth0.client.userInfo(accessToken, (err, profile) => {
  if (profile) {
    // Get the user’s nickname and profile image
    var nickname = profile.nickname;
    var picture = profile.picture;
  }
});
```

[**Angular 2** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/spa-implementation-angular2#3-get-the-user-profile)**での実装を参照してください** 。

### スコープに基づいた条件付きUI要素の表示

ユーザーの`scope`に基づいて、特定のUI要素を表示または非表示にしたい場合があります。ユーザーに発行されたスコープを決めるには、最初に認可プロセスで要求されたスコープを保存する必要があります。ユーザーが認可されると、`authResult`で`scope`も返されます。

`authResult`の`scope`が空だと、要求したすべてのスコープが認められたことになります。`authResult`の`scope`が空でない場合は、異なる一連のスコープが認められ、`authResult.scope`にあるスコープを使用すべきであることを意味します。

[**Angular 2** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/spa-implementation-angular2#4-display-ui-elements-conditionally-based-on-scope)**での実装を参照してください** 。

### APIの呼び出し

APIから安全なリソースにアクセスするには、認証されたユーザーのアクセストークンを、送信される要求に入れる必要があります。これには、`Bearer`スキームを使用して、`Authorization`ヘッダー内でアクセストークンを送る必要があります。

[**Angular 2** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/spa-implementation-angular2#5-call-the-api)**での実装を参照してください** 。

### アクセストークンの更新

安全のため、ユーザーアクセストークンのライフタイムは短くすることが推奨されます。<Tooltip data-tooltip-id="react-containers-DefinitionTooltip-0" href="/docs/ja-jp/glossary?term=auth0-dashboard" tip="Auth0 Dashboard: サービスを構成するためのAuth0の主製品。" cta="用語集の表示">Auth0 Dashboard</Tooltip>でAPIを作成する場合、デフォルトのライフタイムは`7200`秒（2時間）ですが、APIごとに制御できます。

いったん期限が切れたアクセストークンは、APIのアクセスに利用できなくなります。再びアクセスを得るには、新たなアクセストークンを得る必要があります。

最初のアクセストークンを取得する際に使った認証フローを繰り返すことで、新しいアクセストークンを取得できます。しかしこれは、SPAでは最適な方法とはいえません。ユーザーを現在のタスクからリダイレクトして再び認証フローを完了させるような手間をかけさせたくない場合があるからです。

そのような場合は、[サイレント認証](https://auth0.com/docs/api-auth/tutorials/silent-authentication)を使うとよいでしょう。サイレント認証で使われる認証フローでは、Auth0はリダイレクトでのみ返答して、ログインページで返答することはありません。しかし、このためにはユーザーはすでに[シングルサインオン](https://auth0.com/docs/sso)経由でログインしている必要があります。

[**Angular 2** ](https://auth0.com/docs/architecture-scenarios/application/spa-api/spa-implementation-angular2#6-renew-the-access-token)**での実装を参照してください** 。
