> ## 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 の Node.js 実装

# 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) + "*****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>;
};

<div id="nodejs-api-implementation-spas-api">
  # Node.js API の実装 (SPA + 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アプリケーションフレームワーク](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/) を追加します。
* **cors**: このモジュールは、[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) を有効にするためのサポートを追加します。API は、Web ブラウザー上で別のドメインで動作する Single-Page Application から呼び出されるため、これが必要です。
* **jwks-rsa**: このライブラリは、JWKS (JSON Web Key Set) エンドポイントから RSA 署名鍵を取得します。`expressJwtSecret` を使用すると、<Tooltip tip="JSON Web Token (JWT): 2 者間でクレームを安全に表現するために使用される標準的な ID トークン形式（および多くの場合アクセストークン形式）。" cta="用語集を見る" href="/docs/ja-jp/glossary?term=JWT">JWT</Tooltip> ヘッダー内の `kid` に基づいて、`express-jwt` に適切な署名鍵を渡す secret provider を生成できます。詳細については、[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 のリクエストボディを解析するミドルウェアです。受信したリクエストストリームの body 全体を抽出し、扱いやすい形で `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"});
})

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

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

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

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

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

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

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

// timesheets API endpoint を作成 - コードは省略
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](/docs/ja-jp/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>

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

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

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

これで、特定の エンドポイント を実行するために JWT に特定の scope が含まれていることを確認するには、ミドルウェアに `jwtAuthz(...)` の呼び出しを追加するだけで済みます。

追加の依存関係が 1 つ必要です。**express-jwt-authz** ライブラリは express-jwt と組み合わせて使用され、[JWT](/docs/ja-jp/secure/tokens/json-web-tokens) を検証するとともに、目的の エンドポイント を呼び出すために必要な permissions が含まれていることを確認します。詳しくは、[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);
})

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

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

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

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

ただし、timesheets アプリケーションでは、ユーザーのメールアドレスを一意の識別子として使用したいと考えています。

まず最初に、ユーザーのメールアドレスを アクセストークン に追加するルールを作成する必要があります。Dashboard の [Rules section](https://manage.auth0.com/#/rules%7D) に移動し、**Create 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` は、claim に一意の名前を付け、標準の OIDC claim の名前と重複しないようにするために使われます。ただし、Auth0 では、名前空間付きと名前空間なしの両方のカスタム claim をサポートしています。カスタム claim の詳細については、[Create Custom Claims](/docs/ja-jp/secure/tokens/json-web-tokens/create-custom-claims) を参照してください。

次に、API 内で `req.auth` から claim の値を取得し、タイムシートのエントリに関連付ける一意のユーザー 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);
});
```
