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

# サーバーアプリ＋API：APIのためのNode.jsの実装

> サーバークライアント＋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>;
};

[サーバー＋APIアーキテクチャーシナリオ](https://auth0.com/docs/architecture-scenarios/application/server-api)の一環として、Node.jsでタイムシートAPIを実装する方法について説明します。実装したソリューションについての情報は、シナリオを参照してください。

Node.js APIの実装で使用する全ソースコードは、[こちらのGitHubリポジトリ](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-api/node)でご覧いただけます。

## 1.APIエンドポイントを定義する

[Express Webアプリケーションフレームワーク](http://expressjs.com/)を使用してNode.js APIを構築します。

### package.jsonファイルを作成する

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"
}
```

### 依存関係をインストールする

次に、依存関係を設定する必要があります。以下のモジュールを使用します。

* **express** :このモジュールは[Express Webアプリケーションフレームワーク](https://expressjs.com/)を追加します。
* **jwks-rsa** ：このライブラリーは、JWKS（JSON Web Key Set）エンドポイントからRSA署名鍵を取得します。`expressJwtSecret`を使用すると<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>ヘッダーの`kid`に基づいて`express-jwt`に正しい署名鍵を提供するシークレットプロバイダーを生成することができます。詳細については、「[node-jwks-rsa GitHubリポジトリ](https://github.com/auth0/node-jwks-rsa)」を参照してください。
* **express-jwt** ：このモジュールは、Node.jsアプリケーションでJWTトークンを使用してHTTP要求を認証します。JWTを使用した作業を容易にする、いくつかの機能が提供されています。詳細については「[express-jwt GitHubリポジトリ](https://github.com/auth0/express-jwt)」を参照してください。
* **body-parser** ：これはNode.js本文解析ミドルウェアです。やり取りが簡単なものとして、受信要求ストリームから本文全部を抽出し、`req.body`で公開します。詳細および代替については、 「body-parser GitHubリポジトリ」を参照してください。

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

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

### エンドポイントを実装する

APIディレクトリに移動し、`server.js`ファイルを作成します。コードに必要なこと：

* 依存関係を設定する。
* 要求本文解析ミドルウェアを有効にする。
* エンドポイントを実装する。
* APIサーバーを起動する。

実装例を以下に示します。

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

// enable the use of request body parsing middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

// create timesheets upload API endpoint
app.post('/timesheets/upload', function(req, res){
  res.status(201).send({message: "This is the POST /timesheets/upload endpoint"});
})

// launch the API Server at localhost:8080
app.listen(8080);
```

`node server`を使用してAPIサーバーを起動し、`localhost:8080/timesheets/upload`にHTTP POST要求を行います。「`This is the POST /timesheets/upload endpoint`」というメッセージを含むJSON応答が表示されます。

これでエンドポイントが入手できましたが、それは誰でも呼び出すことができます。次の段落に進んで、この問題を解決する方法を確認します。

## 2. APIエンドポイントをセキュリティ保護する

トークンを検証するには、[express-jwt](https://github.com/auth0/express-jwt#usage)ミドルウェアが提供する`jwt`関数と`jwks-rsa`パッケージを使用してAuth0から公開鍵を取得します。ライブラリーは以下を行います。

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

コードでは次の手順に従います。

* アクセストークンを検証するミドルウェア関数を作成する。
* ルートでミドルウェアを使用できるようにする。

これはまた、タイムシートエントリーをローカルデータベースまたは、希望する他のストレージメカニズムに保存するロジックを実装する良い機会でもあります。以下はサンプル実装を示します（簡潔にするために一部のコードは省略しています)。

export const codeExample = `// set dependencies - code omitted

// enable the use of request body parsing middleware - code omitted

// Create middleware for checking the JWT
const checkJwt = jwt({
  // Dynamically provide a signing key based on the kid in the header and the signing keys provided by the JWKS endpoint.
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: \`https://{yourDomain}/.well-known/jwks.json\`
  }),

  // Validate the audience and the issuer.
  audience: process.env.AUTH0_AUDIENCE,
  issuer: \`https://{yourDomain}/\`,
  algorithms: ['RS256']
});

// create timesheets API endpoint
app.post('/timesheets/upload', checkJwt, function(req, res){
  var timesheet = req.body;

  // Save the timesheet entry to the database...

  //send the response
  res.status(201).send(timesheet);
})

// launch the API Server at localhost:8080 - code omitted`;

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

ここでサーバーを起動して`localhost:8080/timesheets`にHTTP POSTを実行すると、エラーメッセージ`Missing or invalid token`が表示されるはずです（要求でアクセストークンを送信しなかったためこれは正確です）。

また、作業シナリオをテストするためには、次を実行する必要があります。

* アクセストークンを取得する。詳しい取得方法については、以下をご覧ください：[アクセストークンを取得する](https://auth0.com/docs/architecture-scenarios/application/server-api#get-an-access-token)
* APIを呼び出し、値`Bearer ACCESS_TOKEN`を指定して`Authorization`ヘッダーを要求に追加します（`ACCESS_TOKEN`は最初の手順で取得したトークンの値）。

## 3. クライアントの権限を確認する

この手順では、クライアントに権限（または`スコープ`）があるかを確認する機能を実装に追加して、タイムシートをアップロードするためにエンドポイントを使用します。特にトークンのスコープが正しく、`batch:upload`であることを確認することが目的です。

そのためには、`express-jwt-authz` Node.jsパッケージを利用するので、それをプロジェクトに追加します。

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

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

```javascript lines theme={null}
// set dependencies - some code omitted
const jwtAuthz = require('express-jwt-authz');

// Create middleware for checking the JWT

// Enable the use of request body parsing middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

// Batch upload endpoint
app.post('/timesheets/upload', checkJwt, jwtAuthz(['batch:upload']), function(req, res){
  var timesheet = req.body;

  // Save the timesheet entry to the database...

  //send the response
  res.status(201).send(timesheet);
});

// launch the API Server at localhost:8080 - code omitted
```

このスコープを含まないトークンを使用してAPIを呼び出すHTTPステータスコード`403`の「Forbidden（禁止）」というエラーメッセージが表示されます。APIからこのスコープを削除すると、これをテストすることができます。

これで作業完了です！完了です。
