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

> Server Client + API アーキテクチャシナリオ向けの API の Node.js 実装

# Server Apps + API: API の Node.js 実装

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>;
};

[Server + API Architecture Scenario](/docs/ja-jp/get-started/architecture-scenarios/server-application-api)の一環として、Node.jsでTimesheets 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-endpoint">
  ## ステップ 1. API エンドポイントを定義する
</div>

Node.js API の構築には、[Express Webアプリケーションフレームワーク](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アプリケーションフレームワーク](https://expressjs.com/) を追加します。
* **jwks-rsa**: このライブラリは、JWKS (JSON Web Key Set) エンドポイントから RSA 署名鍵を取得します。`expressJwtSecret` を使用すると、<Tooltip tip="JSON Web Token (JWT): 2 者間でクレームを安全にやり取りするために使われる標準的な ID Token 形式（また、多くの場合は Access Token 形式）です。" cta="用語集を見る" href="/docs/ja-jp/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 のリクエストボディを解析するミドルウェアです。受信した request ストリームからボディ全体を取り出し、`req.body` として扱いやすい形で利用できるようにします。詳しい情報や代替手段については、body-parser GitHub repository を参照してください。

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

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

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

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

* 依存関係を設定する。
* リクエストボディを解析するミドルウェアを有効にする。
* エンドポイントを実装する。
* API サーバーを起動する。

以下は実装例です。

```javascript lines theme={null}
// 依存関係を設定する
const express = require('express');
const app = express();
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const bodyParser = require('body-parser');

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

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

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

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

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

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

トークンを検証するために、[express-jwt middleware](https://github.com/auth0/express-jwt#usage) が提供する `jwt` 関数と、Auth0 から公開鍵を取得するための `jwks-rsa` パッケージを使用します。これらのライブラリの役割は次のとおりです。

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

コードでは、次の手順で進めます。

* <Tooltip tip="アクセストークン: API へのアクセスに使用される、opaque string または JWT の形式の認可資格情報。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=access+token">アクセストークン</Tooltip>を検証するミドルウェア関数を作成します。
* ルートでそのミドルウェアを使えるようにします。

また、タイムシートのエントリをローカルデータベースや任意のストレージに保存するロジックを実装するにも、ちょうどよいタイミングです。以下はサンプル実装です (一部のコードは簡潔にするため省略しています) 。

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

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

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

  // audience と issuer を検証します。
  audience: process.env.AUTH0_AUDIENCE,
  issuer: \`https://{yourDomain}/\`,
  algorithms: ['RS256']
});

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

  // タイムシートエントリをデータベースに保存します...

  // レスポンスを返します
  res.status(201).send(timesheet);
})

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

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

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

正常に動作するケースもテストするには、次のことを行う必要があります。

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

<div id="step-3-check-the-client-permissions">
  ## ステップ 3. クライアントの権限を確認する
</div>

このステップでは、タイムシートをアップロードするために、クライアントがこのエンドポイントを利用する権限 (または `scope`) を持っているかどうかを確認できるように実装を追加します。特に、トークンに正しいスコープ、つまり `batch:upload` が含まれていることを確認したいと考えています。

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

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

これで、特定のエンドポイントを実行する際に JWT に特定のスコープが含まれていることを確認するには、ミドルウェアに `jwtAuthz(...)` の呼び出しを追加するだけです。以下はサンプル実装です (一部のコードは簡潔さのため省略しています) :

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

// JWTを検証するミドルウェアを作成

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

// 一括アップロードエンドポイント
app.post('/timesheets/upload', checkJwt, jwtAuthz(['batch:upload']), function(req, res){
  var timesheet = req.body;

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

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

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

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

以上です。これで完了です！
