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

> SPA + API アーキテクチャにおける API と SPA の設定

# API と SPA の設定（SPA + API）

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>

<div id="define-the-api-endpoints">
  ## API エンドポイントを定義する
</div>

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

<Card title="API エンドポイントとは？">
  **API エンドポイント**とは、オブジェクトを表す一意の URL のことです。このオブジェクトとやり取りするには、アプリケーションからその URL を指定する必要があります。たとえば、orders または customers を返す API がある場合は、`/orders` と `/customers` という 2 つのエンドポイントを設定できます。アプリケーションは、異なる HTTP メソッドを使ってこれらのエンドポイントとやり取りします。たとえば、`POST /orders` で新しい order を作成し、`GET /orders` で 1 件以上の order のデータセットを取得できます。
</Card>

この実装では、定義するエンドポイントは 2 つだけです。1 つは従業員のすべてのタイムシートの一覧を取得するためのもの、もう 1 つは従業員が新しいタイムシートのエントリを作成するためのものです。

`/timesheets` エンドポイントへの `HTTP GET` リクエストで、ユーザーは自分のタイムシートを取得できます。また、`/timesheets` エンドポイントへの `HTTP POST` リクエストで、ユーザーは新しい timesheet を追加できます。

**実装例は** [**Node.js**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/api-implementation-nodejs#1-define-the-api-endpoints) **を参照してください。**

<div id="secure-the-endpoints">
  ### エンドポイントを保護する
</div>

API がヘッダーに bearer <Tooltip tip="アクセストークン: API へのアクセスに使用される、不透明な文字列または JWT 形式の認可資格情報。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=Access+Token">アクセストークン</Tooltip> を含むリクエストを受け取った場合、最初に行うべきことはそのトークンを検証することです。これには一連の手順があり、そのいずれかで検証に失敗した場合は、呼び出し元のアプリに `Missing or invalid token` というエラーメッセージを返して、リクエストを拒否する必要があります。

API で行うべき検証は次のとおりです。

* <Tooltip tip="JSON Web Token (JWT): 2 者間でクレームを安全に表現するために使用される標準の ID トークン形式（また、多くの場合アクセストークン形式でもあります）。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=JWT">JWT</Tooltip> の形式が正しいことを確認する
* 署名を確認する
* 標準クレームを検証する

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  [JWT.io](https://jwt.io/) では、JWT の解析、署名の検証、クレームの検証など、作業の大部分を行えるライブラリの一覧を提供しています。
</Callout>

検証プロセスの一環として、アプリケーションの権限 (スコープ) も確認する必要がありますが、これについてはこのドキュメントの次の段落で別途説明します。

アクセストークンの検証について詳しくは、[アクセストークンを検証する](/docs/ja-jp/secure/tokens/access-tokens/validate-access-tokens) を参照してください。

**実装については** [**Node.js**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/api-implementation-nodejs#2-secure-the-api-endpoints) **を参照してください。**

<div id="check-the-applications-permissions">
  ### アプリケーションの権限を確認する
</div>

ここまでで、JWT が有効であることを確認できました。最後の手順は、保護されたリソースにアクセスするために必要な権限をアプリケーションが持っていることを確認することです。

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

**実装については** [**Node.js**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/api-implementation-nodejs#3-check-the-client-permissions) **を参照してください。**

<div id="determine-user-identity">
  ### ユーザーの識別
</div>

どちらのエンドポイントでも (タイムシート一覧の取得と新しいタイムシートの追加) 、ユーザーの識別が必要になります。

タイムシート一覧を取得する場合は、リクエストを行ったユーザーに属するタイムシートだけを返すためです。また、新しいタイムシートを追加する場合は、そのタイムシートがリクエストを行ったユーザーに紐付けられるようにするためです。

標準的な JWT クレームの 1 つに `sub` クレームがあり、これはクレームの対象となる主体を識別します。Implicit Grant フローでは、このクレームにユーザーの識別情報、つまり Auth0 ユーザーの一意の識別子が含まれます。これを使えば、外部システム内の任意の情報を特定のユーザーに関連付けることができます。

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

**実装は** [**Node.js**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/api-implementation-nodejs#4-determine-the-user-identity) **で確認してください。**

<div id="implement-the-spa">
  ## SPAを実装する
</div>

このセクションでは、このシナリオでSPAをどのように実装するかを見ていきます。

<div id="authorize-the-user">
  ### ユーザーを認証する
</div>

ユーザーを認証するには、[auth0.js ライブラリ](/docs/ja-jp/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 の <Tooltip tip="Client ID: Auth0 から登録済みリソースに付与される識別値。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=Client+Id">Client Id</Tooltip> の値です。[Dashboard](https://manage.auth0.com/#/applications%7D) にある Application の Settings から取得できます。
* **domain**: Auth0 Domain の値です。[Dashboard](https://manage.auth0.com/#/applications%7D) にある Application の Settings から取得できます。
* **responseType**: 使用する認証フローを指定します。**Implicit Flow** を使用する SPA の場合は、`token id_token` に設定する必要があります。`token` の部分は、フローによって URL フラグメントでアクセストークンが返されることを意味し、`id_token` の部分は、<Tooltip tip="ID Token: リソースへのアクセスではなく、クライアント自身のための資格情報。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=ID+Token">ID Token</Tooltip> も返されることを意味します。
* **<Tooltip tip="Audience: 発行されたトークンの対象者を表す一意の識別子。トークン内では aud という名前で表され、その値には ID Token の場合はアプリケーション (Client ID)、アクセストークンの場合は API (API Identifier) の ID が含まれます。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=audience">audience</Tooltip>**: API Identifier の値です。[Dashboard にある API の Settings](https://manage.auth0.com/#/apis%7D) から取得できます。
* **redirectUri**: ユーザーの認証後に Auth0 がリダイレクトする URL です。
* **scope**: ID Token とアクセストークンで返される情報を決定する [スコープ](/docs/ja-jp/get-started/apis/scopes) です。`openid profile` という scope を指定すると、ID Token にユーザープロファイル情報がすべて返されます。また、API を呼び出すために必要な スコープ (この場合は `read:timesheets create:timesheets`) もリクエストする必要があります。これにより、アクセストークンにこれらの スコープ が含まれるようになります。

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

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

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

parseHash が返す authResult オブジェクトの内容は、使用した認証パラメーターによって異なります。含まれる可能性があるのは次のとおりです。

* **idToken**: ユーザープロファイル情報を含む ID トークン JWT
* **accessToken**: **audience** で指定された API 用のアクセストークン。
* **expiresIn**: アクセストークンの有効期限 (秒数) を表す文字列。

トークンをどこに[保存する](/docs/ja-jp/secure/security-guidance/data-security/token-storage)のが最適かを判断してください。シングルページアプリにバックエンドサーバーがある場合、トークンは [Authorization Code Flow](/docs/ja-jp/get-started/authentication-and-authorization-flow/authorization-code-flow) または [Authorization Code Flow with Proof Key for Code Exchange (PKCE)](/docs/ja-jp/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce) を使用して、サーバー側で扱う必要があります。

対応するバックエンドサーバーのないシングルページアプリ (SPA) の場合、SPA はログイン時に新しいトークンをリクエストし、永続化せずメモリ内に保存する必要があります。API 呼び出しを行う際は、そのメモリ内のトークンのコピーを使用します。

SPA でセッションを処理する方法の例については、[JavaScript Single-Page App Quickstart](/docs/ja-jp/quickstart/spa/vanillajs) の [Handle Authentication Tokens](/docs/ja-jp/quickstart/spa/vanillajs#handle-authentication-tokens) セクションを参照してください。

**実装例は** [**Angular 2**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/spa-implementation-angular2#2-authorize-the-user) **をご覧ください。**

<div id="get-the-user-profile">
  ### ユーザープロフィールを取得する
</div>

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

`client.userInfo`メソッドは、返された`authResult.accessToken`を渡して呼び出すことで、ユーザーのプロフィール情報を取得できます。これにより[/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) {
    // ユーザーのニックネームとプロフィール画像を取得する
    var nickname = profile.nickname;
    var picture = profile.picture;
  }
});
```

**実装については** [**Angular 2**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/spa-implementation-angular2#3-get-the-user-profile) **をご覧ください。**

<div id="display-ui-elements-conditionally-based-on-scope">
  ### `scope` に応じて UI 要素を条件付きで表示する
</div>

ユーザーの`scope`に応じて、特定の UI 要素を表示または非表示にすることがあります。ユーザーに付与された`scope`を確認するには、認可プロセスで最初にリクエストした`scope`を保存しておく必要があります。ユーザーの認可が完了すると、`scope`は`authResult`にも返されます。

`authResult`内の`scope`が空であれば、リクエストしたすべてのスコープが付与されたことを意味します。`authResult`内の`scope`が空でない場合は、別のスコープのセットが付与されたことを意味するため、`authResult.scope`に含まれるものを使用してください。

**実装例は** [**Angular 2**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/spa-implementation-angular2#4-display-ui-elements-conditionally-based-on-scope) **を参照してください。**

<div id="call-the-api">
  ### API を呼び出す
</div>

API の保護されたリソースにアクセスするには、認証済みユーザーのアクセストークンを、その API に送信するリクエストに含める必要があります。これを行うには、`Bearer` スキームを使用して、アクセストークンを `Authorization` ヘッダーで送信します。

**実装については** [**Angular 2**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/spa-implementation-angular2#5-call-the-api) **を参照してください。**

<div id="renew-the-access-token">
  ### アクセストークンを更新する
</div>

セキュリティ対策として、ユーザーのアクセストークンの有効期間は短く設定しておくことをおすすめします。<Tooltip tip="Auth0 Dashboard: サービスを設定するための Auth0 の主要製品です。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=Auth0+dashboard">Auth0 Dashboard</Tooltip> で API を作成すると、デフォルトの有効期間は `7200` 秒 (2 時間) ですが、これは API ごとに設定できます。

有効期限が切れると、アクセストークンは API へのアクセスに使用できなくなります。再度アクセスするには、新しいアクセストークンを取得する必要があります。

新しいアクセストークンは、最初のアクセストークンを取得したときと同じ認証フローを再度実行することで取得できます。SPA では、認証フローをもう一度完了するために、ユーザーを現在の作業から離れてリダイレクトしたくない場合もあるため、これは理想的ではありません。

このような場合は、[Silent Authentication](/docs/ja-jp/authenticate/login/configure-silent-authentication) を利用できます。Silent Authentication では、Auth0 はリダイレクトのみを返し、ログインページは表示しない認証フローを実行できます。ただし、これを利用するには、ユーザーがすでに [Single Sign-on (SSO)](/docs/ja-jp/authenticate/single-sign-on) を通じてログインしている必要があります。

**実装については** [**Angular 2**](/docs/ja-jp/get-started/architecture-scenarios/spa-api/spa-implementation-angular2#6-renew-the-access-token) **を参照してください。**
