> ## 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 アーキテクチャ シナリオ向けの Node.js API 実装

# Node.js API 実装（SPAs + 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) + "*****マスク済み*****";
          }
          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>;
};

<div id="nodejs-api-implementation-spas-api">
  # Node.js API 実装 (SPAs + API)
</div>

このドキュメントは SPA + API アーキテクチャシナリオ の一部であり、Node.js で API を実装する方法について説明します。実装するソリューションについては、このシナリオを参照してください。

Node.js API 実装の完全なソースコードは、[こちらの GitHub リポジトリ](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-api/node) にあります。

<div id="step-1-define-the-api-endpoints">
  ## ステップ 1. API エンドポイントを定義する
</div>

Node.js API の構築には、[Express web application framework](http://expressjs.com/) を使用します。

<div id="create-a-packagejson-file">
  ### package.json ファイルを作成する
</div>

API 用のフォルダーを作成し、その中に移動して `npm init` を実行します。これにより、`package.json` ファイルが作成されます。

デフォルト設定のままでも、必要に応じて変更してもかまいません。

このサンプルの `package.json` は次のようになります。

```json lines theme={null}
{
  "name": "timesheets-api",
  "version": "1.0.0",
  "description": "API used to add timesheet entries for employees and contractors",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/auth0-samples/auth0-pnp-timesheets.git"
  },
  "author": "Auth0",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/auth0-samples/auth0-pnp-timesheets/issues"
  },
  "homepage": "https://github.com/auth0-samples/auth0-pnp-timesheets#readme"
}
```

<div id="install-the-dependencies">
  ### 依存関係をインストールする
</div>

次に、依存関係を設定します。使用するモジュールは次のとおりです。

* **express**: このモジュールは、[Express web application framework](https://expressjs.com/) を追加します。
* **cors**: このモジュールは、[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) を有効にするためのサポートを追加します。これは、Web ブラウザー内の別のドメインで実行されるシングルページアプリケーションから API が呼び出されるため、必要です。
* **jwks-rsa**: このライブラリは、JWKS (JSON Web Key Set) エンドポイントから RSA 署名鍵を取得します。`expressJwtSecret` を使用すると、<Tooltip tip="JSON Web Token (JWT): 2 者間でクレームを安全に表現するために使用される標準的な IDトークン 形式（および多くの場合は アクセストークン 形式）。" cta="用語集を表示" href="/ja/docs/glossary?term=JWT">JWT</Tooltip> ヘッダー内の `kid` に基づいて、適切な署名鍵を `express-jwt` に提供するシークレットプロバイダーを生成できます。詳しくは、[node-jwks-rsa GitHub repository](https://github.com/auth0/node-jwks-rsa) を参照してください。
* **express-jwt**: このモジュールを使用すると、Node.js アプリケーションで JWT を使って HTTP リクエストを認証できます。JWT を扱いやすくするための複数の関数も提供されています。詳しくは、[express-jwt GitHub repository](https://github.com/auth0/express-jwt) を参照してください。
* **body-parser**: これは Node.js のリクエストボディを解析する middleware です。受信したリクエストストリームのボディ全体を抽出し、扱いやすい形で `req.body` として公開します。詳しい情報といくつかの代替手段については、body-parser GitHub repository を参照してください。

これらの依存関係をインストールするには、次を実行します。

```bash wrap lines theme={null}
npm install express cors express-jwt jwks-rsa body-parser express-jwt-authz --save
```

<div id="implement-the-endpoints">
  ### エンドポイントを実装する
</div>

API ディレクトリに移動し、`server.js` ファイルを作成します。コードでは次のことを行います。

* 依存関係をインストールします。
* エンドポイントを実装します。
* API サーバーを起動します。

以下はサンプル実装です。

```javascript lines theme={null}
const express = require('express');
const app = express();
const { expressjwt: jwt } = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');

// CORSを有効にする
app.use(cors());

// リクエストボディ解析ミドルウェアを有効にする
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

// タイムシートAPIエンドポイントを作成する
app.post('/timesheets', function(req, res){
  res.status(201).send({message: "This is the POST /timesheets endpoint"});
})

// APIサーバーをlocalhost:8080で起動する
app.listen(8080);
```

`node server`で API サーバーを起動し、`localhost:8080/timesheets` に HTTP POST リクエストを送信します。`This is the POST /timesheets endpoint` というメッセージを含む JSON レスポンスが返されるはずです。

これでエンドポイントは用意できましたが、現時点では誰でも呼び出せてしまいます。これをどのように解決するかは、次の段落で確認してください。

<div id="step-2-secure-the-api-endpoints">
  ## ステップ 2. API エンドポイントを保護する
</div>

トークンを検証するために、[express-jwt middleware](https://github.com/auth0/express-jwt#usage) が提供する `jwt` 関数と、シークレットを取得するための `jwks-rsa` を使用します。これらのライブラリは次の処理を行います。

1. `express-jwt` はトークンをデコードし、リクエスト、ヘッダー、ペイロードを `jwksRsa.expressJwtSecret` に渡します。
2. 次に `jwks-rsa` は JWKS エンドポイントからすべての署名鍵をダウンロードし、そのいずれかが JWT のヘッダー内の `kid` と一致するかを確認します。受信した `kid` と一致する署名鍵がない場合は、エラーがスローされます。一致するものがあれば、適切な署名鍵を `express-jwt` に渡します。
3. その後 `express-jwt` は独自のロジックを続行し、トークンの署名、有効期限、`audience`、`issuer` を検証します。

コードで実施する手順は次のとおりです。

* <Tooltip tip="アクセストークン: API へのアクセスに使用される、不透明な文字列または JWT の形式の認可資格情報。" cta="用語集を表示" href="/ja/docs/glossary?term=Access+Token">アクセストークン</Tooltip> を検証するミドルウェア関数を作成します。
* ルートでミドルウェアを使用できるようにします。

タイムシートを実際にデータベースへ保存するコードを記述することもできます。以下はサンプル実装です (簡潔にするため一部のコードは省略しています) 。

export const codeExample = `// 依存関係を設定 - コードは省略

// CORS を有効化 - コードは省略

// JWT を検証するミドルウェアを作成
const checkJwt = jwt({
  // ヘッダー内の kid と JWKS エンドポイントで提供される署名鍵に基づいて、署名鍵を動的に取得
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: \`https://{yourDomain}/.well-known/jwks.json\`
  }),

  // オーディエンスと Issuer を検証
  audience: '{YOUR_API_IDENTIFIER}', //Dashboard > APIs で確認できる API の audience に置き換えます
  issuer: 'https://{yourDomain}/',
  algorithms: [ 'RS256' ]
});

// リクエストボディを解析するミドルウェアの使用を有効化 - コードは省略

// タイムシート API エンドポイントを作成 - コードは省略
app.post('/timesheets', checkJwt, function(req, res){
  var timesheet = req.body;

  // タイムシートをデータベースに保存...

  // レスポンスを送信
  res.status(201).send(timesheet);
});
// localhost:8080 で API サーバーを起動 - コードは省略`;

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

ここでサーバーを起動し、`localhost:8080/timesheets` に HTTP POST を送信すると、`Missing or invalid token` というエラーメッセージが返されるはずです (リクエストでアクセストークンを送信していないため、これは想定どおりです) 。

動作するケースもテストするには、次の手順を実行します。

* アクセストークンを取得します。取得方法の詳細については、[Get an Access Token](/ja/docs/get-started/architecture-scenarios/server-application-api#get-an-access-token) を参照してください。
* リクエストに `Authorization` ヘッダーを追加し、その値に `Bearer ACCESS_TOKEN` を指定して API を呼び出します (`ACCESS_TOKEN` は最初の手順で取得したトークンの値です) 。

<div id="step-3-check-the-application-permissions">
  ## ステップ 3. アプリケーションの権限を確認する
</div>

このステップでは、タイムシートを作成するために、アプリケーションがエンドポイントを使用するための権限 (または`scope`) を持っているかどうかを確認する機能を実装に追加します。特に、トークンに正しいスコープ (`batch:upload`) が含まれていることを確認します。

これを行うには、`express-jwt-authz` Node.js パッケージを使用するため、プロジェクトに追加してください。

```bash lines theme={null}
npm install express-jwt-authz --save
```

これで、特定のエンドポイントにアクセスするために必要なスコープが JWT に含まれていることを確認するには、ミドルウェアに `jwtAuthz(...)` の呼び出しを追加するだけです。

追加の依存関係を 1 つ追加します。**express-jwt-authz** ライブラリは express-jwt と組み合わせて使用され、[JWT](/ja/docs/secure/tokens/json-web-tokens) を検証したうえで、目的のエンドポイントを呼び出すために必要な適切な権限があることを確認します。詳細については、[express-jwt-authz GitHub repository](https://github.com/auth0/express-jwt-authz) を参照してください。

以下はサンプル実装です (簡潔にするため、一部のコードは省略しています) :

```javascript lines theme={null}
// 依存関係の設定 - 一部のコードは省略
const jwtAuthz = require('express-jwt-authz');

// CORSを有効化 - コードは省略

// JWTを検証するミドルウェアを作成 - コードは省略

// リクエストボディ解析ミドルウェアの使用を有効化 - コードは省略

// タイムシートAPIエンドポイントを作成
app.post('/timesheets', checkJwt, jwtAuthz(['create:timesheets'], { customUserKey: 'auth' }), function(req, res){
  var timesheet = req.body;

  // タイムシートをデータベースに保存...

  // レスポンスを送信
  res.status(201).send(timesheet);
})

// APIサーバーをlocalhost:8080で起動 - コードは省略
```

このスコープを含まないトークンで API を呼び出すと、HTTP ステータスコード `403` のエラーメッセージ Forbidden が返されるはずです。これを確認するには、API からこのスコープを削除してください。

<div id="step-4-determine-the-user-identity">
  ## ステップ 4. ユーザーの識別情報を特定する
</div>

JWT の検証に使用する `express-jwt` ミドルウェアは、JWT に含まれる情報を `req.auth` にも設定します。ユーザーを一意に識別するために `sub` クレームを使用する場合は、`req.auth.sub` をそのまま使用できます。

ただし、このタイムシートアプリケーションでは、一意の識別子としてユーザーのメールアドレスを使用します。

まず、ユーザーのメールアドレスをアクセストークンに追加する Rule を作成する必要があります。Dashboard の [Rules section](https://manage.auth0.com/#/rules%7D) に移動し、**Create Rule** ボタンをクリックします。

Rule には、たとえば `Add email to Access Token` のようなわかりやすい名前を付け、次のコードを使用します。

```javascript lines theme={null}
function (user, context, callback) {
  const namespace = 'https://api.exampleco.com/';
  context.accessToken[namespace + 'email'] = user.email;
  callback(null, user, context);
}
```

`namespace` は、クレームに一意の名前を付け、標準の OIDC クレーム名と競合しないようにするために使用されます。ただし、Auth0 は名前空間付きと名前空間なしの両方のカスタムクレームをサポートしています。カスタムクレームの詳細については、[Create Custom Claims](/ja/docs/secure/tokens/json-web-tokens/create-custom-claims) を参照してください。

次に、API 内で `req.auth` からクレームの値を取得し、それをタイムシートのエントリに関連付ける一意のユーザー ID として使用できます。

```js lines theme={null}
app.get('/timesheets', checkJwt, jwtAuthz(['read:timesheets'], { customUserKey: 'auth' }), function(req, res) {
  var timesheet = req.body;

  // タイムシートエントリを現在のユーザーに関連付ける
  var userId = req.auth['https://api.exampleco.com/email'];
  timesheet.user_id = userId;

  // タイムシートをデータベースに保存する...

  //レスポンスを送信する
  res.status(201).send(timesheet);
});
```
