【API Gateway・Cognitoユーザープール】Cognitoを使用した認証・認可を実装してみた

はじめに

過去にApiGatewayでの認可実装について、APIキー+使用料プランを用いた実装、Lambda Authorizerを使用した実装を検証してきました。
https://cloud5.jp/oishi-20250605/
https://cloud5.jp/oishi-20250601/

今回はCognito Authorizerを使用した実装を検証していきたいと思います。

今回もリクエストにはCurlコマンドを使用し、IDトークンを乗せて検証していきます。

Cognitoから IDトークンを取得する方法については、クライアントシークレットなしでトークンを取得する方法と、クライアントシークレットを使用する方法(SECRET_HASHを計算)の2つのアプローチを実施します。

Cognitoとは

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html

Amazon Cognito はウェブアプリとモバイルアプリ用のアイデンティティプラットフォームです。これは、ユーザーディレクトリ、認証サーバー、および OAuth 2.0 アクセストークンと AWS 認証情報の認証サービスです。Amazon Cognito を使用すると、組み込みのユーザーディレクトリ、エンタープライズディレクトリ、Google や Facebook などのコンシューマー ID プロバイダーからユーザーを認証および認可できます。

Cognito とは上記ドキュメントに記載のように、ウェブアプリやモバイルアプリに今や必須となった認証・認可の機能を実装する際に利用できるサービスとなっています。
Cognito には大きく分けて二種類のサービスが存在します。

認証と認可の違い
認証とはアクセスしてきたユーザーが何者なのかを確認することを指します。
一方で、認可とはアクセスしてきたユーザーにどの権限があるかを確認することを指します。
基本的に、権限を確認するためにはアクセスしてきたユーザーを特定する必要があるため、認可の前には認証を行うことが多いです。

Cognito ユーザープール

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools.html

Amazon Cognito ユーザープールは、ウェブおよびモバイルアプリケーションの認証と認可のためのユーザーディレクトリです。

Cognito ユーザープールとは上記ドキュメントにも記載があるように認証と認可両方を行うことができるサービスです。
単体で認証サーバーとしての機能を持ち、API やエンドポイントを通じて認証を実施することが可能です。
また、外部 Idp と連携を行い、認証機能を外出しし AWS サービスと外部 Idp の仲介を行うといった使い方も可能です。

API Gateway や別サービスと組み合わせることで権限の確認を行う認可を行うことも可能になりますが、基本的に認証を行うサービスと認識いただければと思います。

Cognito ID プール

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-identity.html

Amazon Cognito アイデンティティプールは、 AWS 認証情報と交換できるフェデレーションアイデンティティのディレクトリです。ID プールは、サインインしているか、まだ識別していないかにかかわらず、アプリケーションのユーザーに一時的な AWS 認証情報を生成します。

Cognito ID プール(Cognito アイデンティティプールとも言います)とは主に認可を行うサービスとなります。
ユーザーが認証時に取得できる情報をもとに、一時的な AWS 認証情報を生成し提供します。
生成される AWS 認証情報は IAM ロールとなるため、アクセス可能なサービスなどを絞った権限を設定するなど任意の権限をユーザーの属性ごとに与えることが可能になります。

ユーザーの認証情報は Cognito ユーザープールから取得したものも利用可能ですが、その他 Google や Apple などサードパーティ製の認証サーバーから取得したものも利用いただけます。

今回はID プールは使用しません。

概要

構成は以下のような形です。

① クライアント → Cognito : ユーザー認証リクエスト
クライアントが、Cognito ユーザープールに対して ユーザー認証リクエストを送信します。
認証方法は、ユーザー名 + パスワードを使用していきます。

② Cognito → クライアント : IDトークン発行
認証に成功すると、Cognitoは IDトークン、アクセス トークン、リフレッシュトークンをクライアントに返します。 このうち、IDトークンをAPI Gatewayのオーソライザーで使用します。

③ クライアント → API Gateway : IDトークンを付けてAPIリクエスト
クライアントは、Cognitoから取得したIDトークンを、リクエストの際、Authorizationヘッダーに設定し、API Gatewayに送信します。

④ API Gateway → Cognito : トークンの検証
API Gatewayは、オーソライザーとして設定された Cognito User Poolに IDトークンを送信し、有効性を検証します。
具体的には、トークンの署名検証、発行元(Issuer)の確認、有効期限の確認などが行われます。

⑤ Cognito → API Gateway : トークンの判定
トークンが有効な場合 → API GatewayはリクエストをLambdaに転送します。
トークンが無効な場合 → API Gatewayは「401 Unauthorized」エラーをクライアントに返します。

⑥ API Gateway → Lambda : ユーザー情報付きでリクエスト転送
Cognitoで認証された場合、API Gatewayはユーザー情報(sub、email、cognito:groups など)を含めてLambdaにリクエストを送信します。

⑦ Lambda → API Gateway : APIレスポンスを返す
Lambdaはリクエストを処理し、結果を API Gatewayに返します。

⑧ API Gateway → クライアント : HTTPレスポンスを返す
API Gatewayは、Lambdaからのレスポンスを受け取り、クライアントに返します。

テスト用Lambdaの作成

下記のような簡単なLambdaを作成しておきます。

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

ユーザープールの作成(クライアントシークレットなし)

Cognitoのユーザープールを作成していきます。

赤枠部分を入力していきます。

アプリケーションを定義は「シングルページアプリケーション(SPA)」を選択します。
他選択肢を選ぶ目安は以下になるかと思います。

1. 従来のウェブアプリケーション

  • クライアントシークレットを使用
  • サーバーサイドでレンダリングされるウェブアプリ
  • バックエンドでシークレットを安全に管理できる環境

2. シングルページアプリケーション (SPA)

  • ブラウザで動作するJavaScriptアプリ(React、Vue.js等)
  • クライアントシークレット不使用
  • PKCE(Proof Key for Code Exchange)を使用

3. モバイルアプリ

  • iOS、Androidのネイティブアプリ
  • クライアントシークレット不使用
  • リフレッシュトークンによる長期セッション維持

4. Machine to Machine アプリケーション

  • ユーザーの介入なしのシステム間認証
  • Client Credentials Grant フローを使用
  • API間通信、バックエンドサービス間認証

サインイン識別子のオプションは、「ユーザー名」のみにします。
今回は、ユーザープールのログイン画面は使用しませんが、ログイン画面で入力する項目の選択オプションになります。
サインアップのための必須属性にメールアドレスまたは電話番号を必須属性として選択する必要がありますので、「email」を選択してユーザープールを作成していきます。

ユーザープール名の変更

作成したら、概要からユーザープール名を変更しておきましょう。

認証フローの設定

アプリケーションクライアントに移動し、以下の箇所から編集をしていきます。

今回の認証方法は、ユーザー名 + パスワードを使用していきますので「ユーザー名とパスワード (ALLOW_USER_PASSWORD_AUTH) を使用してサインインします」に、チェックを入れて保存します。

ユーザーの作成

次にユーザーの作成をしていきます。

以下のような形で、ユーザーを作成します。

初期パスワードの変更

今回のようにマネージドコンソールから手動でユーザーを作成すると、パスワードの変更が必要になります。
変更をしないと、トークンの発行は行われません。

変更方法は2つあります。
①管理者による直接設定(チャレンジ不要)
AWS CLIで--permanentをつけて、変更すると、永続パスワードに変更ができます。
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminSetUserPassword.html

//例:①管理者による直接設定(チャレンジ不要)
aws cognito-idp admin-set-user-password \
    --region ap-northeast-1 \
    --user-pool-id <User Pool ID> \
    --username <ユーザー名> \
    --password <パスワード> \
    --permanent

②ユーザー自身による変更(チャレンジ必要)

//例:②ユーザー自身による変更(チャレンジ必要)
//※初回ログイン時
aws cognito-idp respond-to-auth-challenge \
  --client-id <クライアントID> \
  --challenge-name NEW_PASSWORD_REQUIRED \
  --session "<initiate-authのレスポンスに含まれるSession>" \
  --challenge-responses Username=<ユーザー名>,NEW_PASSWORD=<新しいパスワード>

ログイン済みユーザーが自分でパスワードを変更する場合は、「aws cognito-idp change-password」で変更します。この時--access-tokenが必要になります。

チャレンジとは
チャレンジ(Challenge) = Cognitoがユーザーに対して「追加情報の提供」や「特定のアクション」を求めることです。

ユーザー自身によるパスワード変更(チャレンジ必要)

管理者として、変更すれば楽ですが、今回はチャレンジをして変更してきます。
チャレンジには--sessionが必要になります。

//アクセストークンの取得
aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id <クライアントID> \
  --auth-parameters USERNAME=<ユーザー名>,PASSWORD=<更新前のパスワード>

上記を実行すると以下のような形で返ってきます。

{
    "ChallengeName": "NEW_PASSWORD_REQUIRED",
    "Session": "アクセストークン",
    "ChallengeParameters": {
        "USER_ID_FOR_SRP": "ユーザー名",
        "requiredAttributes": "[]",
        "userAttributes": "{\"email\":\"xxxxxxxxxx\"}"
    }
}

アクセストークンを入手できたら、それを元に以下を実行して、チャレンジをします。

aws cognito-idp respond-to-auth-challenge \
  --client-id <クライアントID> \
  --challenge-name NEW_PASSWORD_REQUIRED \
  --session "<initiate-authのレスポンスに含まれるSession>" \
  --challenge-responses USERNAME=<ユーザー名>,NEW_PASSWORD=<新しいパスワード>

成功すると、以下のような形で返ってきます。そして、ユーザーのステータスが、確認済みに変更されていればパスワード変更完了です!

{
    "ChallengeParameters": {},
    "AuthenticationResult": {
        "AccessToken": "",
        "ExpiresIn": 3600,
        "TokenType": "Bearer",
        "RefreshToken": "",
        "IdToken": ""
    }
}

Cognito Authorizerの設定

API GatewayのオーソライザーをCognitoで作成しましょう。
トークンのソースは、一般的なAuthorizationで設定します。

次にメソッドリクエストに先ほど先ほど作成したオーソライザーを設定します。
完了したらデプロイを忘れずに行いましょう。

オーソライザーのテスト

作成したオーソライザーで認可のテストができます。
以下のCurlコマンドを実行し、IDトークンを取得します。

IDトークンの取得

IDトークンは下記のinitiate-authを使用して取得します。
取得したIDトークンをトークンの値に入力し、200になれば成功です。

//アクセストークンの取得
aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id <クライアントID> \
  --auth-parameters USERNAME=<ユーザー名>,PASSWORD=<パスワード>

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html

POSTでリクエスト

次にCurlコマンドでPOSTリクエストをしてみましょう。

TOKEN="IDトークン"
curl -X POST https://xxxx.execute-api.ap-northeast-1.amazonaws.com/test/ \
-H "Authorization: Bearer $TOKEN"

実行し、Lambda関数の結果(Hello from Lambda!)が返ってくれば成功です。

ユーザープールの作成(クライアントシークレットあり)

クライアントシークレットなしの検証ができたので、次にクライアントシークレットありで検証していきます。
まず、従来のアプリケーションを選択し、アプリケーションを作成します。

作成したらALLOW_USER_PASSWORD_AUTHにチェックを入れて保存します。

クライアントシークレットが発行されているのを確認します。

SECRET_HASHの取得

先ほどと同じようにinitiate-authでIDトークンを取得しようとすると、以下のようなエラーが出ます。
これは、「SECRET_HASHがないがためにトークンが取得できない」ことが原因でエラーが発生しています。

https://repost.aws/ja/knowledge-center/cognito-unable-to-verify-secret-hash

Client 5d0v53044ojq2m8bft29hav0v1 is configured with secret but SECRET_HASH was not received

:::message
SECRET_HASHとは
AWS Cognitoのユーザー認証で使用される追加のセキュリティチェックのための署名ハッシュ値です。これは、Cognitoの「アプリクライアント(App Client)」にクライアントシークレット(Client Secret)が設定されている場合に必須となります。

このSECRET_HASHは、ユーザー名(USERNAME)とアプリクライアントID(ClientId)を結合した文字列に対して、クライアントシークレットをキーとしたHMAC-SHA256で署名を行い、それをBase64エンコードしたものです。AWSはこのSECRET_HASHを受け取って、クライアントが正しく署名できているかどうかを検証します。これにより、不正なクライアントアプリからのログインリクエストを防止する仕組みです。
:::

下記コマンドでSECRET_HASHを取得していきます。

echo -n "<ユーザーネーム><クライアントID>" | openssl dgst -sha256 -hmac "<クライアントシークレット>" -binary | base64

IDトークンの取得

--auth-parametersSECRET_HASHをのせて、initiate-authを実行すると、IDトークンの取得が可能になります。

//アクセストークンの取得
aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id <クライアントID> \
  --auth-parameters USERNAME=<ユーザー名>,PASSWORD=<パスワード>,SECRET_HASH=<シークレットハッシュ>

おわりに

今回はCognitoユーザープール+IDトークンを使用して、APIの認可を実装してきました。
Cognitoの中でもアプリケーションの種類が4種類あり、どれを使うべきかやクライアントシークレットの有り無しでの実装の差異など、Cognitoの理解を深めることができました。
CognitoにはIDトークン以外にもアクセストークンやリフレッシュトークンという種類もあるため、今後はそれらの違い等も検証していきたいと思います。

参考

https://dev.classmethod.jp/articles/get-started-with-amazon-cognito-now-1/
https://dev.classmethod.jp/articles/get-started-with-amazon-cognito-now-2/
https://techblog.asia-quest.jp/202503/amazon-cognito-authorizer-to-control-access-to-amazon-api-gateway
https://zenn.dev/kazu_o/articles/c4e4c7b96122c3

Last modified: 2025-06-12

Author