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

# Node.jsでのAPIの実装（モバイルアプリ + API）

> モバイル + APIアーキテクチャーシナリオ向けのNode.jsでの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>;
};

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

// Enable CORS - 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: '{YOUR_API_IDENTIFIER}', //replace with your API's audience, available at Dashboard > APIs
  issuer: 'https://{yourDomain}/',
  algorithms: [ 'RS256' ]
});

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

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

  // Save the timesheet to the database...

  //send the response
  res.status(201).send(timesheet);
});
// launch the API Server at localhost:8080 - code omitted`;

本ドキュメントはモバイル + APIアーキテクチャーシナリオの一部で、APIをNode.jsで実装する方法を説明します。Node.jsでのAPI実装で使用する全ソースコードは、[こちらのGitHubリポジトリ](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-api/node)でご覧いただけます。

実装したソリューションについての情報は、シナリオを参照してください。

<AccordionGroup>
  <Accordion title="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/)を追加します。
    * **cors** ：このモジュールは[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)を有効にするためのサポートを追加します。これは、APIがWebブラウザー内の別のドメインで実行されるシングルページアプリケーションから呼び出されるために必要です。
    * **jwks-rsa** ：このライブラリーは、JWKS（JSON Web Key Set）エンドポイントからRSA署名鍵を取得します。`expressJwtSecret`を使用すると、JWTヘッダーの`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`上でインターフェイスしやすいものとして公開します。

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

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

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

    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');

    // Enable CORS
    app.use(cors());

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

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

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

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

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

  <Accordion title="2 APIエンドポイントを保護する">
    トークンを検証するには、[express-jwt](https://github.com/auth0/express-jwt#usage)ミドルウェアが提供する`jwt`関数と`jwks-rsa`を使用してシークレットを取得します。ライブラリーは以下を行います。

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

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

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

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

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

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

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

    * アクセストークンを取得する。詳しい取得方法については、「[アクセストークンを取得する](/docs/ja-jp/architecture-scenarios/application/server-api#get-an-access-token)」を参照してください。
    * APIを呼び出し、値`Bearer ACCESS_TOKEN`を指定して`Authorization`ヘッダーを要求に追加します（`ACCESS_TOKEN`は最初のステップで取得したトークンの値）。
  </Accordion>

  <Accordion title="3 アプリの権限を確認する">
    このステップでは、アプリケーションに権限（またはスコープ）があるかを確認する機能を追加して、タイムシートを作成するためにエンドポイントを使用します。特に、トークンのスコープが正しく`batch:upload`であることを確認することが目的です。

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

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

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

    依存関係を追加します。**express-jwt-authz** ライブラリーはexpress-jwtと組み合わせて使用され、[JWT](https://auth0.com/docs/tokens/concepts/jwts)を検証し、目的のエンドポイントを呼び出すための正しい権限を持っていることを保証します。詳細については、「[express-jwt-authz GitHubリポジトリ](https://github.com/auth0/express-jwt-authz)」を参照してください。

    以下はサンプル実装を示します（簡潔にするために一部のコードは省略しています)。

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

    // Enable CORS - code omitted

    // Create middleware for checking the JWT - code omitted

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

    // create timesheets API endpoint
    app.post('/timesheets', checkJwt, jwtAuthz(['create:timesheets'], { customUserKey: 'auth' }), function(req, res){
      var timesheet = req.body;

      // Save the timesheet 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からこのスコープを削除と、これをテストすることができます。
  </Accordion>

  <Accordion title="4 ユーザーIDを決定する">
    JWTの検証に使われる`express-jwt`ミドルウェアは、JWTに含まれる情報を`req.user`に設定します。`sub`クレームを使用してユーザーを一意に識別したい場合は、`req.user.sub`を使用できます。タイムシートアプリケーションでは、ユーザーのメールアドレスを一意の識別子として使用することが目的です。

    ##### アクションを作成する

    まず、アクセストークンにユーザーのメールアドレスを追加する[アクションを新規作成](/docs/ja-jp/customize/actions/write-your-first-action)します。

    1. [［Auth0 Dashboard］ > ［Actions（アクション）］ > ［Library（ライブラリー）］](https://manage.auth0.com/#/actions/library)に移動し、**［Build Custom（カスタム作成）］** を選択します。

    2. アクションの説明的な **［Name（名前）］** を入力し（たとえば、`Add email to access token`）、**［Login / Post-Login（ログイン/ログイン後）］** トリガー、**［Create（作成）］** の順に選択します。

    3. Actionsコードエディターを見つけて、次のJavaScriptコードをコピーし、**［Save Draft（下書きを保存）］** を選択して変更を保存します。

       ```lines theme={null}
       exports.onExecutePostLogin = async (event, api) => {
         const namespace = 'https://my-app.example.com';
         api.accessToken.setCustomClaim(`${namespace}/email`, event.user.email);
       }
       ```

           <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
             `namespace`（名前空間）を使うと、クレームの名前が標準のOIDCクレームや内部サービスと衝突しない、一意のものになります。名前空間のあるクレームと名前空間のないクレームの制約やガイドラインについては、「[カスタムクレームを作成する](/docs/ja-jp/secure/tokens/json-web-tokens/create-custom-claims)」をお読みください。
           </Callout>

    4. Actionsコードエディタのサイドバーから［Test（テスト）］(再生アイコン)を選択し、**［Run（実行）］** を選択して[コードをテスト](/docs/ja-jp/customize/actions/test-actions)します。

    5. アクションを公開する準備ができたら、**［Deploy（デプロイ）］** を選択します。

    ##### ログインフローにアクションを追加する

    次に、作成したアクションを[ログインフロー](https://manage.auth0.com/#/actions/flows/login/)に追加します。フローにアクションをアタッチする方法の詳細については、「[初めてのアクションを作成する](/docs/ja-jp/customize/actions/write-your-first-action)」の「アクションをフローにアタッチする」セクションをお読みください。

    ##### 一意の識別子を取得する

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

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

      // Associate the timesheet entry with the current user
      var userId = req.auth['https://api.exampleco.com/email'];
      timesheet.user_id = userId;

      // Save the timesheet to the database...

      //send the response
      res.status(201).send(timesheet);
    });
    ```
  </Accordion>
</AccordionGroup>
