---
title: "Cognitoで認証基盤を実装する手順：ユーザープール作成からaws-jwt-verifyでのJWT検証まで"
url: "https://www.issoh.co.jp/tech/details/17445/"
published: 2026-09-06
updated: 2026-10-18
categories: ["AWS"]
publisher: "株式会社一創"
---

# Cognitoで認証基盤を実装する手順：ユーザープール作成からaws-jwt-verifyでのJWT検証まで

Amazon Cognito は、アプリケーションにサインアップとサインインの機能を持たせる AWS の認証サービスです。ユーザープールは管理画面のウィザードで数分あれば作れますが、本番で事故が起きるのはその先です。CLI や API で作ると既定値が緩い設定が残る、トークン検証で `aud` の扱いを誤る、ログアウトしたはずのトークンがまだ API を通る、といったものです。

この記事では、ユーザープールの作成から JWT 検証の実装、トークン失効、API Gateway と Application Load Balancer（ALB）への組み込みまでを、公式ドキュメントの記載と手元での実行結果をもとに追います。検証ライブラリは aws-jwt-verify 5.2.1 と PyJWT 2.15.1 を2026年10月18日に実行して確かめました。Cognito の概要や料金の全体像は[Amazon Cognito（コグニート）とは？料金・できること・使い方をCLIとコード例で解説【2026年10月】](/tech/details/3108/)に任せ、本稿は手を動かす部分に絞ります。

## まとめ

- **作成後に変えられない項目を先に決める**：サインイン識別子と必須属性は作成後に変更できません。カスタム属性は50個まで追加できますが、削除も型の変更もできません。
- **移行はパスワードごと持ち込める**：CSV インポートで bcrypt などのハッシュを指定すれば、利用者にパスワードの再設定をさせずに移せます。
- **CLI で作るアプリケーションクライアントは既定値を締める**：属性の読み書き権限を指定しないと全属性が読み書き可能になり、`PreventUserExistenceErrors` は API の既定が `LEGACY`（ユーザーの有無がエラーで分かる）です。
- **トークン検証はライブラリに任せる**：aws-jwt-verify は token\_use・client\_id・iss・期限・署名の不一致をそれぞれ別のエラーで弾きました。PyJWT で書くなら、リソースバインディングを指定しないアクセストークンには `aud` が無いため `client_id` と `token_use` を自分で照合します。
- **ログアウトは2種類ある**：RevokeToken は特定のリフレッシュトークンとその子トークンだけを失効させ、GlobalSignOut はそのユーザーの全トークンを失効させます。どちらでも、失効済みトークンは署名と期限だけを見るローカル検証を通ってしまいます。
- **API の前段で止める**：REST API なら Cognito オーソライザー、ALB 配下の Web アプリなら ALB の認証アクションで、アプリに到達する前に未認証リクエストを返せます。

## ユーザープールとアプリケーションクライアントを作成する

Cognito の実装は、ユーザーの入れ物である「ユーザープール」と、そこへアクセスする側の登録である「アプリケーションクライアント」を作るところから始まります。機能プランはユーザープール単位で決まり、新規作成時の既定は Essentials です（[ユーザープールの機能プラン](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-sign-in-feature-plans.html)）。

### 作成後に変えられないサインイン識別子・必須属性・カスタム属性

ユーザー名の代わりにメールアドレスや電話番号でサインインさせる設定と、必須属性は、[ユーザープール属性のドキュメント](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html)で作成後に変更できないと明記されています。将来メールでのログインを許す可能性があるなら、作成時点で含めておきます。

カスタム属性は1つのプールに50個まで、値の長さは最大2,048文字です。追加した属性は削除も名前の変更もできず、データ型・変更可否・長さの制約も変えられません。もう1つ見落としやすいのが型で、数値型で定義したカスタム属性も ID トークンには文字列として書き込まれます。`custom:tenant_id` を数値で比較するコードは、トークンから読んだ値を変換してから比べます。

```
aws cognito-idp create-user-pool \
  --pool-name my-app-users \
  --user-pool-tier ESSENTIALS \
  --username-attributes email \
  --auto-verified-attributes email \
  --policies '{"PasswordPolicy":{"MinimumLength":12,"RequireUppercase":true,"RequireNumbers":true,"RequireSymbols":true}}' \
  --admin-create-user-config '{"AllowAdminCreateUserOnly":true}' \
  --deletion-protection ACTIVE \
  --region ap-northeast-1
```

`AllowAdminCreateUserOnly` を `true` にすると管理者だけがユーザーを作れます。`false` のままでシークレットなしのクライアントを公開すると、クライアント ID を知っている人は誰でも `SignUp` API で登録できます。会員登録を受け付ける一般向けサービス以外では `true` にしておきます。MFA は作成後にも変えられますが、必須に切り替えると既存ユーザーは次回サインイン時に登録を求められます。方式ごとの設定は[Cognito MFAの設定手順：TOTP・SMS・メールの有効化と必須化の判断](/tech/details/18510/)で扱っています。

### アプリケーションクライアントのシークレット有無で変わる認証の形

ブラウザで動く SPA やモバイルアプリはコードが利用者の手元に配られるため、クライアントシークレットを生成しません。埋め込んでも取り出せるからです。この場合は認可コードフローに PKCE を組み合わせます（[PKCEとは？認可コード横取り攻撃を防ぐcode\_verifierの実装と検証設計を解説](/tech/details/16405/)）。サーバー側で完結する Web アプリやマシン間通信ではシークレットを生成し、トークンエンドポイントへの要求に Basic 認証ヘッダーで付けます。

### CLI・API作成クライアントの既定値と4項目の設定方針

管理画面のウィザードと違い、[CreateUserPoolClient の API リファレンス](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API%5FCreateUserPoolClient.html)どおりに省略した項目は次の既定値になります。Terraform や CloudFormation から作る場合も同じ API を通るので、指定しない限りこの状態です。

| 項目                         | 省略時の値                    | 指定する値の例 |
| -------------------------- | ------------------------ | ------- |
| 属性の読み書き権限                  | 全属性を読み書き可                | 必要な属性だけ |
| PreventUserExistenceErrors | LEGACY                   | ENABLED |
| ExplicitAuthFlows          | SRP・REFRESH・CUSTOM\_AUTH | 使うフローだけ |
| EnableTokenRevocation      | 有効                       | 有効のまま   |

属性の権限が効くのは、サインインしたユーザー自身が持つアクセストークンです。Cognito の API（`InitiateAuth` など）で認証したアクセストークンの既定の scope は `aws.cognito.signin.user.admin` で、このスコープは [アクセストークンのドキュメント](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html)によれば、クライアントに許した範囲で属性の読み書きを認めます。`custom:role` のような権限判定用の属性を書き込み可能のまま残すと、利用者が `UpdateUserAttributes` で自分の権限を書き換えられます。権限の情報は同じユーザープール内のすべてのクライアントの書き込み権限から外すか、Cognito に持たせずアプリ側のデータベースで `sub` に紐付けて管理します。

```
aws cognito-idp create-user-pool-client \
  --user-pool-id ap-northeast-1_xxxxxxxxx \
  --client-name my-spa-client \
  --no-generate-secret \
  --explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \
  --prevent-user-existence-errors ENABLED \
  --enable-token-revocation \
  --read-attributes email email_verified name \
  --write-attributes email name \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes openid email profile \
  --allowed-o-auth-flows-user-pool-client \
  --callback-urls "https://app.example.com/callback" \
  --supported-identity-providers COGNITO \
  --region ap-northeast-1
```

既存のクライアントを直すときは `update-user-pool-client` の扱いに注意します。[UpdateUserPoolClient](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API%5FUpdateUserPoolClient.html) は指定しなかった項目を既定値に戻すため、変えたい1項目だけを渡すとコールバック URL やスコープが消えます。先に `describe-user-pool-client` で現在値を取り、全項目を渡し直します。コールバック URL は完全一致で照合されるので、末尾のスラッシュやポート番号まで揃えないと `redirect_mismatch` になります。

## 機能プランを Lite・Essentials・Plus から選ぶ

機能プランはユーザープールごとに1つで、同じプール内のクライアントごとに分けることはできません。新しく作るユーザープールで実装に影響する境界は次の通りです（[機能プランの比較表](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-sign-in-feature-plans.html)より）。2024年11月22日より前から高度なセキュリティ機能を有効にしていた既存プールには、Lite のままでも[トークン生成前トリガー](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html)の V2\_0 でアクセストークンを書き換えられる例外があります。

| 機能                      | Lite        | Essentials | Plus |
| ----------------------- | ----------- | ---------- | ---- |
| サインアップ・サインイン、グループ       | 可           | 可          | 可    |
| SAML・OIDC・ソーシャル連携       | 可           | 可          | 可    |
| M2M（クライアント認証情報）         | 可           | 可          | 可    |
| TOTP・SMS の MFA          | 可           | 可          | 可    |
| マネージドログイン               | 不可（クラシックのみ） | 可          | 可    |
| パスキー・パスワードレス            | 不可          | 可          | 可    |
| メールのワンタイムコードによる MFA     | 不可          | 可          | 可    |
| アクセストークンのクレーム書き換え       | 不可          | 可          | 可    |
| 脅威保護（漏えいパスワード・不審なサインイン） | 不可          | 不可         | 可    |

料金は月間アクティブユーザー（MAU）単位で、[料金ページ](https://aws.amazon.com/cognito/pricing/)では Lite と Essentials に月10,000 MAU の無料枠があり、Plus には無料枠がありません。実装で効くのは2点です。SAML・OIDC のフェデレーション経由はプランに関係なく無料枠が50 MAU なので、企業の IdP とつなぐ社内システムは利用者が50人を超えると課金が始まります。M2M はトークン要求1回ごとの課金で無料枠が無いので、アクセストークンは有効期限まで再利用します。単価の内訳は冒頭で挙げた Cognito の概要記事で扱っています。

パスキーやマネージドログインを使うなら Essentials が下限、不正ログインの検知と証跡を求められるなら Plus にします。パスキーの実装は[Cognitoのパスキー実装手順：WebAuthnサインインの有効化とSDKでの登録・認証](/tech/details/18516/)、Entra ID との SAML 連携は[Cognito SAML連携の手順：Entra IDでSSOを組む設定と属性マッピング](/tech/details/18512/)にまとめています。

## マネージドログインのドメインを設定する

ログイン画面を自作しない場合は、Cognito がホストする画面を使います。[マネージドログインのドキュメント](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html)によると、マネージドログインかクラシックなホストされた UI かの選択（ブランディングバージョン）はドメイン単位で、そのドメインを使う全クライアントに一律で効きます。プレフィックスドメインは追加から最大60秒で使えるようになりますが、独自ドメインの追加やブランディングバージョンの切り替えは反映に数分以上かかることがあります。切り替え直後に画面が変わらなくても、時間を置いてから確かめます。

CLI で作ったクライアントには、もう1つ手順が要ります。管理画面で作ったクライアントにはブランディングのスタイルが自動で割り当てられますが、`CreateUserPoolClient` で作ったクライアントにはスタイルが作られず、`CreateManagedLoginBranding` を呼ぶまでマネージドログインを使えません。既定の見た目で始めるなら次の1行で足ります。なお、ブランディングバージョンを切り替えるとサインイン中のセッションは引き継がれず、利用者は新しい画面で再度サインインします。

```
aws cognito-idp create-managed-login-branding \
  --user-pool-id ap-northeast-1_xxxxxxxxx \
  --client-id xxxxxxxxxxxxxxxxxxxxxxxxxx \
  --use-cognito-provided-values \
  --region ap-northeast-1
```

表示言語は `/oauth2/authorize` に付ける `lang` パラメータで決まり、日本語（`ja`）を含む12言語から選べます。最初の要求で `lang` クッキーが設定され、以降はパラメータなしでも同じ言語で表示されます。画面のカスタマイズと日本語化の手順は[AWS Cognito Hosted UI（マネージドログイン）のカスタマイズ・日本語化と実装手順](/tech/details/3441/)で解説しています。

## 認可コードフローでトークンを受け取る

Cognito は OAuth 2.0 と OpenID Connect の認可サーバーとして動きます。ブラウザを `/oauth2/authorize` に送り、PKCE の `code_challenge` と `code_challenge_method=S256`、応答との照合用の `state` を付けて認可を開始します。戻ってきた `state` を照合し、`code` と元の `code_verifier` をトークンエンドポイントに送ると、ID トークン、アクセストークン、リフレッシュトークンが返ります。

```
curl -X POST "https://my-domain.auth.ap-northeast-1.amazoncognito.com/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=xxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -d "code=RECEIVED_AUTH_CODE" \
  -d "code_verifier=ORIGINAL_CODE_VERIFIER" \
  -d "redirect_uri=https://app.example.com/callback"
```

### IDトークンとアクセストークンの用途と中身の違い

ID トークンにはメールアドレスや氏名などの属性と、発行先クライアントを示す `aud` が入ります。アクセストークンには `scope`、グループ（`cognito:groups`）、発行先を示す `client_id` が入ります。認可要求でリソースバインディングを指定した場合は、対象リソースの識別子を示す `aud` も入ります。2つは別の鍵で署名されるため、同じセッションでも `kid` が一致しません。API の認可はアクセストークンの `scope` とグループで判断し、画面に出す氏名やメールは ID トークンから読みます。JWT の構造そのものは[JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説](/tech/details/13453/)を参照してください。

## JWTをバックエンドで検証する

### 公式ドキュメントが求める6つの確認項目

[JWT の検証手順](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html)が挙げる確認項目は次の6つです。

1. 署名：ヘッダーの `kid` に一致する公開鍵をユーザープールの JWKS から探し、RS256 の署名を検証する
2. `exp`：期限が切れていない
3. `aud`（ID トークン）または `client_id`（アクセストークン）：自分のクライアント ID と一致する
4. `iss`：`https://cognito-idp.{リージョン}.amazonaws.com/{ユーザープールID}` と一致する
5. `token_use`：アクセストークンだけを受けるなら `access`、ID トークンだけなら `id`
6. 失効していない：署名と期限が正しくても、サインアウトや RevokeToken で失効している場合がある

JWKS は `https://cognito-idp.{リージョン}.amazonaws.com/{ユーザープールID}/.well-known/jwks.json` で公開されています。Cognito は署名鍵をローテーションすることがあるため、`kid` をキーにしてキャッシュし、知らない `kid` が来たら取り直します。6つ目は署名検証だけでは確かめられません。後述のトークン失効の章で扱います。

### aws-jwt-verifyでNode.jsの検証ミドルウェアを書く

AWS Labs が公開している [aws-jwt-verify](https://github.com/awslabs/aws-jwt-verify) は、JWKS の取得とキャッシュ、署名検証、1〜5の照合をまとめて引き受けます。`npm install aws-jwt-verify` で導入し、ES Modules として読み込む Express アプリでは、次のミドルウェアを保護対象ルートのハンドラより前に登録します。

```
import { CognitoJwtVerifier } from "aws-jwt-verify";

const verifier = CognitoJwtVerifier.create({
  userPoolId: "ap-northeast-1_xxxxxxxxx",
  tokenUse: "access",
  clientId: "xxxxxxxxxxxxxxxxxxxxxxxxxx",
});

export async function authMiddleware(req, res, next) {
  const header = req.headers.authorization ?? "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : null;
  if (!token) return res.status(401).json({ message: "token required" });
  try {
    const payload = await verifier.verify(token);
    req.user = { sub: payload.sub, scope: payload.scope, groups: payload["cognito:groups"] ?? [] };
    next();
  } catch {
    return res.status(401).json({ message: "invalid token" });
  }
}
```

`create` には `scope` と `groups` も渡せます。特定の操作だけ `orders/write` スコープや `admin` グループを要求したいときは、要求ごとに検証器を分けるとハンドラ側に if 文を書かずに済みます。

### aws-jwt-verifyが不正なトークンを弾く条件の実測

どの不一致がどのエラーになるかを、手元で確かめました。RSA 2048ビットの鍵を生成して Cognito と同じ形のトークンを署名し、`cacheJwks` で検証器に公開鍵を渡して aws-jwt-verify 5.2.1（Node.js 26.5.0）で `verify` を呼んだ結果です。

| 入力したトークン                      | 結果                             |
| ----------------------------- | ------------------------------ |
| 正しいアクセストークン                   | 通過                             |
| ID トークンを tokenUse: access で検証 | CognitoJwtInvalidTokenUseError |
| 別クライアントの client\_id           | CognitoJwtInvalidClientIdError |
| exp が10秒前                     | JwtExpiredError                |
| 同じリージョンの別プールの iss             | ParameterValidationError       |
| ペイロードのグループを書き換え               | JwtInvalidSignatureError       |
| scope 不足（orders/write を要求）    | JwtInvalidScopeError           |
| groups: admin を要求しグループなし      | CognitoJwtInvalidGroupError    |
| exp が10秒前・graceSeconds: 30    | 通過                             |
| キャッシュに無い kid                  | JWKS を取りに行く                    |

運用で効く結果が2つあります。1つは `iss` 違いがトークンの不正ではなく `ParameterValidationError` になる点で、公式ドキュメントにも、別のプールや別リージョンのトークンを渡したときに起きると書かれています。ログでこのエラーが続くなら、攻撃より先に環境変数のプール ID の取り違えを疑います。もう1つは `graceSeconds` で、サーバー間の時計のずれを吸収できますが、その秒数だけ期限切れのトークンを受け入れます。指定するなら数十秒に留めます。

### PyJWTでaudの無いアクセストークンを検証するときの落とし穴

Python で PyJWT を使う場合は、`aud` の扱いが落とし穴になります。PyJWT 2.15.1 で同じ形のトークンを検証した結果は次の通りでした。

| トークン     | audience の指定  | 結果                        |
| -------- | ------------- | ------------------------- |
| アクセストークン | クライアント ID を渡す | MissingRequiredClaimError |
| アクセストークン | 渡さない          | 通過                        |
| ID トークン  | 渡さない          | InvalidAudienceError      |
| ID トークン  | aud 検証を無効化    | 通過（ID トークンなのに通る）          |

認可要求でリソースバインディングを指定しない限り、Cognito のアクセストークンには `aud` が無いため、`audience` を渡すと失敗します（リソースバインディング付きのトークンなら、`audience` に対象リソースの識別子を指定します）。かといって渡さずに済ませると、`client_id` と `token_use` は誰も見ていません。表の最後の行のように、`aud` 検証を切った関数には ID トークンも通ります。リソースバインディングなしのアクセストークンを受ける API では、次のように2つを自分で照合します。RS256 の検証には `python -m pip install 'PyJWT[crypto]==2.15.1'` で暗号ライブラリごと導入します。

```
import jwt

REGION = "ap-northeast-1"
USER_POOL_ID = "ap-northeast-1_xxxxxxxxx"
CLIENT_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
ISSUER = f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}"
jwks_client = jwt.PyJWKClient(f"{ISSUER}/.well-known/jwks.json")

def verify_access_token(token: str) -> dict:
    signing_key = jwks_client.get_signing_key_from_jwt(token).key
    # この例はリソースバインディングなし（audなし）のトークン専用
    # aud付きトークンでは、期待するリソース識別子を audience に指定する
    claims = jwt.decode(
        token, signing_key, algorithms=["RS256"], issuer=ISSUER,
        options={"require": ["exp", "iss", "sub", "token_use", "client_id"]},
    )
    if claims["token_use"] != "access":
        raise jwt.InvalidTokenError("token_use is not access")
    if claims["client_id"] != CLIENT_ID:
        raise jwt.InvalidTokenError("client_id mismatch")
    return claims
```

この関数に ID トークンを渡すと `client_id` が無いため `MissingRequiredClaimError`、別クライアントのアクセストークンは `client_id mismatch` で止まることを確認しています。`algorithms` を固定し、`verify_signature=False` のようなデバッグ用の指定を本番コードに残さないことも合わせて守ります。

## サインアウトとトークン失効を設計する

「ログアウトしたのに API が通る」という問い合わせは、Cognito の失効の仕組みを知らないと説明できません。[トークン失効のドキュメント](https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html)によると、失効の手段は範囲で分かれます。

| 手段                           | 失効する範囲                  | 認可                                      |
| ---------------------------- | ----------------------- | --------------------------------------- |
| RevokeToken ／ /oauth2/revoke | 渡したリフレッシュトークンと、その子のトークン | シークレット付きクライアントのみ要                       |
| GlobalSignOut                | そのユーザーの全トークン            | aws.cognito.signin.user.admin付きアクセストークン |
| AdminUserGlobalSignOut       | 対象ユーザーの全トークン            | IAM 認証情報                                |

端末ごとのログアウトは RevokeToken で実現できます。その端末のリフレッシュトークンを渡せば、ほかの端末のセッションには影響しません。パスワード変更やアカウント乗っ取りの疑いがあるときは、全端末を落とす GlobalSignOut 系を使います。GlobalSignOut はアクセストークンに `aws.cognito.signin.user.admin` スコープが要るため、前述の例のように `openid email profile` だけを許可したクライアントのトークンからは呼べません。その場合はサーバー側から AdminUserGlobalSignOut を使います。また [GlobalSignOut](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API%5FGlobalSignOut.html) はマネージドログインのセッション Cookie を消さないため、マネージドログインでサインインした利用者はブラウザを `/logout` エンドポイントへ送らないと、次のサインイン画面でパスワードを聞かれずに戻ってきます。RevokeToken を使うにはクライアントでトークン失効が有効になっている必要があり、API で新しく作ったクライアントでは既定で有効です。

失効の限界も公式に書かれています。失効したトークンは Cognito の API では使えなくなりますが、署名と有効期限を確かめる JWT ライブラリで検証すると有効と判定されます。前章の aws-jwt-verify も PyJWT も、Cognito に問い合わせずに検証するので同じです。アクセストークンの有効期限を短くすると、失効後も使える時間を減らせます。失効を即時に効かせたい API では、アプリ側で失効状態を管理し、各リクエストで確認します。

### トークンの有効期限とリフレッシュトークンのローテーション

アクセストークンと ID トークンの有効期限は既定1時間で、5分から1日まで、リフレッシュトークンは既定30日で、60分から10年まで設定できます。管理者画面のように権限が強いクライアントほどアクセストークンを短くし、リフレッシュで補います。

リフレッシュトークンのローテーションを有効にすると、更新のたびに新しいリフレッシュトークンが発行され、元のトークンは無効になります。ただし、再試行用の猶予期間を設定した場合は、最大60秒間、元のトークンも有効です。[リフレッシュトークンのドキュメント](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-refresh-token.html)では、ローテーションしても有効期間は最初のサインインから数えた `RefreshTokenValidity` のまま延びないこと、`REFRESH_TOKEN_AUTH` とは併用できず `GetTokensFromRefreshToken` またはトークンエンドポイントの `refresh_token` グラントで更新できることが示されています。既存アプリで有効にするなら、クライアントの `ALLOW_REFRESH_TOKEN_AUTH` を外す前に、更新処理を `GetTokensFromRefreshToken` へ書き換えておきます。寿命の設計そのものは[リフレッシュトークンとは？トークンローテーションの仕組みと更新フロー・寿命設計を実装者向けに解説](/tech/details/16409/)で整理しています。

## API GatewayとALBの前段で認証する

### API GatewayのCognitoオーソライザーはスコープ指定で受けるトークンが変わる

REST API では、ユーザープールを `COGNITO_USER_POOLS` 型のオーソライザーとして登録します。[API Gateway のドキュメント](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html)では、メソッドの認可スコープを空にすると ID トークン、スコープを指定するとアクセストークンで認可する設定になります。スコープを指定したメソッドでは、トークンのスコープがどれか1つでも一致すれば通り、一致しなければ401です。

```
aws apigateway create-authorizer \
  --rest-api-id xxxxxxxxxx \
  --name cognito-authorizer \
  --type COGNITO_USER_POOLS \
  --provider-arns "arn:aws:cognito-idp:ap-northeast-1:123456789012:userpool/ap-northeast-1_xxxxxxxxx" \
  --identity-source "method.request.header.Authorization" \
  --region ap-northeast-1
```

オーソライザーが確かめるのはトークンが有効かどうかまでで、そのユーザーが当該レコードを操作してよいかは判定しません。ユーザーごとのデータ分離は、トークンの `sub` をバックエンドで読んで絞り込む実装が別に要ります。HTTP API の JWT オーソライザーや Lambda オーソライザーとの使い分けは[API Gatewayの認証：Cognito・JWT・Lambdaオーソライザー・IAM認可の使い分けと設定例](/tech/details/18493/)にまとめました。

### ALB配下のアプリによるx-amzn-oidc-dataの署名検証

コンテナや EC2 の Web アプリを ALB で受けている構成では、リスナールールに認証アクションを置けます。未認証のアクセスは ALB がログイン画面へ送り、認証後はユーザー情報を `x-amzn-oidc-*` ヘッダーに載せてターゲットへ渡します。[ALB のドキュメント](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html)によると、署名されるのは `x-amzn-oidc-data` だけで、ES256 で署名されます。`x-amzn-oidc-identity` と `x-amzn-oidc-accesstoken` は署名のない平文です。

アプリ側では `x-amzn-oidc-data` の署名を `https://public-keys.auth.elb.{リージョン}.amazonaws.com/{kid}` の公開鍵で検証し、ヘッダーの `signer` が自分の ALB の ARN と一致するかを確かめてから `sub` を使います。ターゲットのセキュリティグループは ALB からの通信だけを許可します。ここが緩いと、ヘッダーを偽造したリクエストが直接届きます。リスナールールの設定とヘッダー検証のコードは[ALBとCognitoで認証を後付けする手順：リスナールール設定とヘッダー検証](/tech/details/18501/)で解説しています。

## 既存システムからユーザーを移行する

移行の方式は3つあります。[CSV インポートのドキュメント](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-using-import-tool.html)によると、既定のインポートはパスワード以外の属性だけを移し、ユーザーは `RESET_REQUIRED` の状態で入るため初回にパスワードの再設定が要ります。インポートジョブにハッシュ方式（`BCRYPT`・`SCRYPT`・`ARGON2ID`・`PBKDF2_SHA256` のいずれか）を指定し、CSV に `password_hash` 列を入れれば、既存のパスワードのまま `CONFIRMED` で取り込めます。対応方式でも上限があり、bcrypt は cost 12まで、Argon2id はメモリ19,456KiB・反復2・並列1までです。これより重い設定で保存しているシステムのハッシュは取り込めません。1つのジョブ内のハッシュは同じ方式に揃える必要があり、初回サインインが済むまでその利用者は SRP 認証を使えません。前章のように `ALLOW_USER_SRP_AUTH` だけに絞ったクライアントでは、移行期間中に `ALLOW_USER_PASSWORD_AUTH` を開ける必要がある点を計画に入れます。

旧システムのハッシュ方式が4種のどれにも当たらない場合は、ユーザー移行の Lambda トリガーを使います。プールにいないユーザーがサインインしたときに旧システムへ問い合わせ、認証が通ればプールに作成する方式で、移行期間中は旧システムを止められません。再設定を求められたユーザーが使うパスワードリセットの実装は[Cognitoのパスワードリセット実装：ForgotPasswordと管理者リセット・ロックアウト設計](/tech/details/18514/)、移行後のサインイン監査は[Cognitoのログ取得手順：CloudTrailとユーザーアクティビティログでサインインを監査する](/tech/details/18518/)にまとめています。

## Cognitoを採用する条件と見送る場面

Cognito が向くのは、システムが AWS 上にあり、API Gateway や ALB の前段で認証を済ませる構成です。認証の要件がサインアップ、サインイン、ソーシャルログイン、MFA、パスキーの範囲に収まり、直接サインインまたはソーシャル連携の利用がアカウントまたは AWS Organizations 単位の無料枠10,000 MAUに収まれば、その区分の Essentials の MAU 料金はかかりません。SAML・OIDC連携の無料枠は別に50 MAUで、SMS送信などの関連料金も別途確認します。フロントエンドを Amplify で作る場合は[Amplify×Cognito認証の実装手順：defineAuthと既存プールの参照](/tech/details/18508/)が続きの手順です。

見送るべき場面もはっきりしています。認証の途中に独自の同意画面を挟む、外部 API の結果で分岐する、といった要件はマネージドログインの枠に収まらず、カスタム認証チャレンジの Lambda トリガーが積み上がります。CUSTOM\_AUTH を使うなら、SRP などほかの認証フローを有効のまま残すとそれを迂回されるので、クライアントの `ExplicitAuthFlows` を絞ります。SAML・OIDC 連携が主体で利用者が数百人規模の社内システムは、50 MAU を超えた分が課金対象になるため、ほかの IdP と費用を比べます。オンプレミスを含めて認証を1か所に集めたい場合は、自前でホストできる Keycloak が候補です（[Keycloakとは？Oktaとの違いと連携手順｜Auth0・Cognito比較で導入を判断【2026年10月】](/tech/details/7663/)）。

既存の会員基盤からの移行や、複数システムをまたぐ認証の設計まで含めて検討する場合は、[認証基盤・ID管理システム開発](https://www.issoh.co.jp/service/system/identity/)で要件整理から構築まで支援しています。

## よくある質問

### Cognitoのユーザープールとアイデンティティプールはどちらを使えばよいですか？

アプリケーションへのログインが目的ならユーザープールだけで足ります。アイデンティティプールが必要なのは、サインインしたユーザーに S3 や DynamoDB へ直接アクセスさせる一時的な AWS 認証情報を配る場合です。API 経由でデータを扱う Web アプリケーションの多くは、ユーザープールだけで完結します。

### aws-jwt-verifyはブラウザやLambdaでも使えますか？

使えます。aws-jwt-verify は Node.js のほか、Web ブラウザと Lambda の Node.js ランタイムで動きます。Lambda では検証器をハンドラの外で `create` しておくと、取得した JWKS が実行環境の再利用中はキャッシュされます。ただしブラウザでの検証は表示制御の補助にとどめ、権限の判定はバックエンドで行います。

### ログアウトしたのにアクセストークンでAPIが通るのはなぜですか？

Cognito のトークンは署名と有効期限を内部に持つ JWT で、失効しても署名は有効なままだからです。Cognito の API は失効済みトークンを拒否しますが、aws-jwt-verify や API Gateway のように署名と期限で検証する側は、有効期限が切れるまで受け入れます。アクセストークンの有効期限を短くするのが基本の対策です。

### Cognitoのグループで管理者だけにAPIを許可するには？

ユーザーをグループに入れると、アクセストークンと ID トークンの `cognito:groups` にグループ名が入ります。aws-jwt-verify なら `groups: "admin"` を指定した検証器で、グループに属さないトークンを `CognitoJwtInvalidGroupError` で弾けます。グループの付け外しは管理者 API で行うため、利用者がトークン経由で自分のグループを変えることはできません。

### マネージドログインを使わず独自のログイン画面を作れますか？

SDK から `InitiateAuth` と `RespondToAuthChallenge` を呼べば画面を自作できます。ただしパスワードリセット、MFA の登録、アカウント確認といった周辺の画面もすべて自作になります。この経路で得たアクセストークンの既定の scope は `aws.cognito.signin.user.admin` です。トークン生成前 Lambda トリガーの V2\_0 以降ではカスタムスコープを追加できます。トリガーを使わずにカスタムスコープで API を分けたい場合はマネージドログイン経由の認可コードフローを使います。

## 関連記事

- [Amazon Cognito（コグニート）とは？料金・できること・使い方をCLIとコード例で解説【2026年10月】](/tech/details/3108/)
- [AWS Cognito Hosted UI（マネージドログイン）のカスタマイズ・日本語化と実装手順](/tech/details/3441/)
- [API Gatewayの認証：Cognito・JWT・Lambdaオーソライザー・IAM認可の使い分けと設定例](/tech/details/18493/)
- [ALBとCognitoで認証を後付けする手順：リスナールール設定とヘッダー検証](/tech/details/18501/)
- [JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説](/tech/details/13453/)

---

出典: [Cognitoで認証基盤を実装する手順：ユーザープール作成からaws-jwt-verifyでのJWT検証まで](<https://www.issoh.co.jp/tech/details/17445/>)（株式会社一創）
