この記事では、Express.js アプリケーションに AWS Cognito を使用し、アクセストークンを検証する認証ミドルウェアの実装方法を説明する。AWS Cognito はセキュリティが強固で広く使用されている認証サービスであり、Webアプリケーションやモバイルアプリケーションでのユーザー認証に適している。ここでは、JWT (JSON Web Tokens) の署名を検証し、有効なトークンのみがリソースへのアクセスを許可されるようにする手順を解説する。

前回の記事: 【Express.js】AWS SDKを使用したCognito認証処理

必要なライブラリと開発環境

まず、以下のライブラリをインストールする。

npm install jsonwebtoken jwks-rsa

jsonwebtokenライブラリは、JWTの生成、署名、および検証を行うためのツールキット。
jwks-rsaライブラリは、公開鍵のセットを提供するJWKS (JSON Web Key Set) エンドポイントから特定の公開鍵を取得し、JWTの署名を検証するために使用される。

実装

設定ファイル: src/config/aws/cognito.ts

まず、AWS Cognito の設定を管理するためのコンフィグクラスを作成する。これには、Cognito ユーザープールのリージョン、ユーザープールID、アプリクライアントIDが含まれる。

export class AWSCognitoConfig {
  private static instance: AWSCognitoConfig;

  private readonly region: string;
  private readonly userPoolId: string;
  private readonly appClientId: string;

  private constructor() {
    // リージョン
    this.region = "ap-northeast-1";
    // ユーザープールID
    this.userPoolId = "xxxxxxxxxxxxxx";
    // アプリクライアントID
    this.appClientId = "xxxxxxxxxxxxxx";
  }

  public static getInstance(): AWSCognitoConfig {
    if (!AWSCognitoConfig.instance) {
      AWSCognitoConfig.instance = new AWSCognitoConfig();
    }
    return AWSCognitoConfig.instance;
  }

  public get getRegion(): string {
    return this.region;
  }

  public get getUserPoolId(): string {
    return this.userPoolId;
  }

  public get getAppClientId(): string {
    return this.appClientId;
  }
}

トークン検証ミドルウェアの実装: src/middleware/authMiddleware.ts

次に、JWTの署名を検証するためのミドルウェアを実装する。このミドルウェアは、リクエストの Authorization ヘッダーからトークンを取得し、そのトークンの署名が AWS Cognito の公開鍵によって検証されるかどうかを確認する。

import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";
import { AWSCognitoConfig } from "../config/aws/cognito";

// Cognito設定情報
const cognitoConfig = AWSCognitoConfig.getInstance();

/**
 * 指定された JWKS URL から公開鍵を取得するためのクライアントを作成
 * このクライアントは、JWTが持っている kid(Key ID)ヘッダーを使って、
 * トークンの署名に使用された正確な公開鍵を見つけ出す
 */
const client = jwksClient({
  // AWS Cognito ユーザープールの JWKS (公開鍵セット) の場所
  jwksUri: `https://cognito-idp.${cognitoConfig.getRegion}.amazonaws.com/${cognitoConfig.getUserPoolId}/.well-known/jwks.json`,
});

/**
 * JWTのkid(キーID)を使用して適切な公開鍵を取得
 */
function getKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) {
  // 指定された kid を持つキーを JWKS から検索
  client.getSigningKey(header.kid!, (err, key) => {
    const signingKey = key?.getPublicKey();
    callback(null, signingKey);
  });
}

/**
 * このミドルウェアはJWTのkid(キーID)を使用して適切な公開鍵を取得し、トークンの署名を検証する。
 * トークンが有効であれば、リクエストオブジェクトにユーザー情報を追加し、処理を次のミドルウェアに渡す。
 * トークンが無効であれば、401 Unauthorizedエラーを返します。
 */
export const checkAuth = (req: Request, res: Response, next: NextFunction) => {
  // リクエストヘッダーからaccessTokenを取得
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) {
    // トークンがセットされていない場合はエラーを返す
    return res.status(401).send({ message: "Authentication token is missing" });
  }

  // トークンの署名を検証
  jwt.verify(
    token,
    getKey,
    {
      algorithms: ["RS256"],
    },
    (err, decoded) => {
      if (err) {
        // トークンが無効な場合は401 Unauthorizedエラーにする
        return res.status(401).send({ message: "Unauthorized: Invalid token" });
      }
      // トークンが有効な場合、デコードされたユーザー情報をリクエストに追加
      req.user = decoded;
      // 次のミドルウェアへ処理を渡す
      next();
    }
  );
};

ルーティング設定: src/routes/jobTypes.ts

認証ミドルウェアを使用して、各エンドポイントにアクセス制限を設ける。今回は事例として、職種情報を登録する処理でトークンの検証を実施することにする。

import express from "express";
import {
  createJobType,
} from "../controllers/JobTypeController";
import { checkAuth } from "../middleware/authMiddleware";

const router = express.Router();

// リソースへのアクセス前に、認証ミドルウェアを実行する
router.post("/job-type/create", checkAuth, createJobType);

export default router;

これで、AWS Cognito を使用してアクセストークンの検証を行う認証ミドルウェアが正しく設定され、Express.js アプリケーションで安全にデータ管理ができるようになった。この実装により、不正なアクセスを防ぎつつ、適切なユーザーのみがリソースにアクセスできるようになる。

カテゴリー: awsNode.js