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月】に任せ、本稿は手を動かす部分に絞ります。
まとめ
- 作成後に変えられない項目を先に決める:サインイン識別子と必須属性は作成後に変更できません。カスタム属性は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 です(ユーザープールの機能プラン)。
作成後に変えられないサインイン識別子・必須属性・カスタム属性
ユーザー名の代わりにメールアドレスや電話番号でサインインさせる設定と、必須属性は、ユーザープール属性のドキュメントで作成後に変更できないと明記されています。将来メールでのログインを許す可能性があるなら、作成時点で含めておきます。
カスタム属性は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・メールの有効化と必須化の判断で扱っています。
アプリケーションクライアントのシークレット有無で変わる認証の形
ブラウザで動く SPA やモバイルアプリはコードが利用者の手元に配られるため、クライアントシークレットを生成しません。埋め込んでも取り出せるからです。この場合は認可コードフローに PKCE を組み合わせます(PKCEとは?認可コード横取り攻撃を防ぐcode_verifierの実装と検証設計を解説)。サーバー側で完結する Web アプリやマシン間通信ではシークレットを生成し、トークンエンドポイントへの要求に Basic 認証ヘッダーで付けます。
CLI・API作成クライアントの既定値と4項目の設定方針
管理画面のウィザードと違い、CreateUserPoolClient の API リファレンスどおりに省略した項目は次の既定値になります。Terraform や CloudFormation から作る場合も同じ API を通るので、指定しない限りこの状態です。
| 項目 | 省略時の値 | 指定する値の例 |
|---|---|---|
| 属性の読み書き権限 | 全属性を読み書き可 | 必要な属性だけ |
| PreventUserExistenceErrors | LEGACY | ENABLED |
| ExplicitAuthFlows | SRP・REFRESH・CUSTOM_AUTH | 使うフローだけ |
| EnableTokenRevocation | 有効 | 有効のまま |
属性の権限が効くのは、サインインしたユーザー自身が持つアクセストークンです。Cognito の API(InitiateAuth など)で認証したアクセストークンの既定の scope は aws.cognito.signin.user.admin で、このスコープは アクセストークンのドキュメントによれば、クライアントに許した範囲で属性の読み書きを認めます。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 は指定しなかった項目を既定値に戻すため、変えたい1項目だけを渡すとコールバック URL やスコープが消えます。先に describe-user-pool-client で現在値を取り、全項目を渡し直します。コールバック URL は完全一致で照合されるので、末尾のスラッシュやポート番号まで揃えないと redirect_mismatch になります。
機能プランを Lite・Essentials・Plus から選ぶ
機能プランはユーザープールごとに1つで、同じプール内のクライアントごとに分けることはできません。新しく作るユーザープールで実装に影響する境界は次の通りです(機能プランの比較表より)。2024年11月22日より前から高度なセキュリティ機能を有効にしていた既存プールには、Lite のままでもトークン生成前トリガーの V2_0 でアクセストークンを書き換えられる例外があります。
| 機能 | Lite | Essentials | Plus |
|---|---|---|---|
| サインアップ・サインイン、グループ | 可 | 可 | 可 |
| SAML・OIDC・ソーシャル連携 | 可 | 可 | 可 |
| M2M(クライアント認証情報) | 可 | 可 | 可 |
| TOTP・SMS の MFA | 可 | 可 | 可 |
| マネージドログイン | 不可(クラシックのみ) | 可 | 可 |
| パスキー・パスワードレス | 不可 | 可 | 可 |
| メールのワンタイムコードによる MFA | 不可 | 可 | 可 |
| アクセストークンのクレーム書き換え | 不可 | 可 | 可 |
| 脅威保護(漏えいパスワード・不審なサインイン) | 不可 | 不可 | 可 |
料金は月間アクティブユーザー(MAU)単位で、料金ページでは Lite と Essentials に月10,000 MAU の無料枠があり、Plus には無料枠がありません。実装で効くのは2点です。SAML・OIDC のフェデレーション経由はプランに関係なく無料枠が50 MAU なので、企業の IdP とつなぐ社内システムは利用者が50人を超えると課金が始まります。M2M はトークン要求1回ごとの課金で無料枠が無いので、アクセストークンは有効期限まで再利用します。単価の内訳は冒頭で挙げた Cognito の概要記事で扱っています。
パスキーやマネージドログインを使うなら Essentials が下限、不正ログインの検知と証跡を求められるなら Plus にします。パスキーの実装はCognitoのパスキー実装手順:WebAuthnサインインの有効化とSDKでの登録・認証、Entra ID との SAML 連携はCognito SAML連携の手順:Entra IDでSSOを組む設定と属性マッピングにまとめています。
マネージドログインのドメインを設定する
ログイン画面を自作しない場合は、Cognito がホストする画面を使います。マネージドログインのドキュメントによると、マネージドログインかクラシックなホストされた 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(マネージドログイン)のカスタマイズ・日本語化と実装手順で解説しています。
認可コードフローでトークンを受け取る
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との違いを実装視点で解説を参照してください。
JWTをバックエンドで検証する
公式ドキュメントが求める6つの確認項目
JWT の検証手順が挙げる確認項目は次の6つです。
- 署名:ヘッダーの
kidに一致する公開鍵をユーザープールの JWKS から探し、RS256 の署名を検証する exp:期限が切れていないaud(ID トークン)またはclient_id(アクセストークン):自分のクライアント ID と一致するiss:https://cognito-idp.{リージョン}.amazonaws.com/{ユーザープールID}と一致するtoken_use:アクセストークンだけを受けるならaccess、ID トークンだけならid- 失効していない:署名と期限が正しくても、サインアウトや 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 は、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 の失効の仕組みを知らないと説明できません。トークン失効のドキュメントによると、失効の手段は範囲で分かれます。
| 手段 | 失効する範囲 | 認可 |
|---|---|---|
| RevokeToken / /oauth2/revoke | 渡したリフレッシュトークンと、その子のトークン | シークレット付きクライアントのみ要 |
| GlobalSignOut | そのユーザーの全トークン | aws.cognito.signin.user.admin付きアクセストークン |
| AdminUserGlobalSignOut | 対象ユーザーの全トークン | IAM 認証情報 |
端末ごとのログアウトは RevokeToken で実現できます。その端末のリフレッシュトークンを渡せば、ほかの端末のセッションには影響しません。パスワード変更やアカウント乗っ取りの疑いがあるときは、全端末を落とす GlobalSignOut 系を使います。GlobalSignOut はアクセストークンに aws.cognito.signin.user.admin スコープが要るため、前述の例のように openid email profile だけを許可したクライアントのトークンからは呼べません。その場合はサーバー側から AdminUserGlobalSignOut を使います。また GlobalSignOut はマネージドログインのセッション Cookie を消さないため、マネージドログインでサインインした利用者はブラウザを /logout エンドポイントへ送らないと、次のサインイン画面でパスワードを聞かれずに戻ってきます。RevokeToken を使うにはクライアントでトークン失効が有効になっている必要があり、API で新しく作ったクライアントでは既定で有効です。
失効の限界も公式に書かれています。失効したトークンは Cognito の API では使えなくなりますが、署名と有効期限を確かめる JWT ライブラリで検証すると有効と判定されます。前章の aws-jwt-verify も PyJWT も、Cognito に問い合わせずに検証するので同じです。アクセストークンの有効期限を短くすると、失効後も使える時間を減らせます。失効を即時に効かせたい API では、アプリ側で失効状態を管理し、各リクエストで確認します。
トークンの有効期限とリフレッシュトークンのローテーション
アクセストークンと ID トークンの有効期限は既定1時間で、5分から1日まで、リフレッシュトークンは既定30日で、60分から10年まで設定できます。管理者画面のように権限が強いクライアントほどアクセストークンを短くし、リフレッシュで補います。
リフレッシュトークンのローテーションを有効にすると、更新のたびに新しいリフレッシュトークンが発行され、元のトークンは無効になります。ただし、再試行用の猶予期間を設定した場合は、最大60秒間、元のトークンも有効です。リフレッシュトークンのドキュメントでは、ローテーションしても有効期間は最初のサインインから数えた RefreshTokenValidity のまま延びないこと、REFRESH_TOKEN_AUTH とは併用できず GetTokensFromRefreshToken またはトークンエンドポイントの refresh_token グラントで更新できることが示されています。既存アプリで有効にするなら、クライアントの ALLOW_REFRESH_TOKEN_AUTH を外す前に、更新処理を GetTokensFromRefreshToken へ書き換えておきます。寿命の設計そのものはリフレッシュトークンとは?トークンローテーションの仕組みと更新フロー・寿命設計を実装者向けに解説で整理しています。
API GatewayとALBの前段で認証する
API GatewayのCognitoオーソライザーはスコープ指定で受けるトークンが変わる
REST API では、ユーザープールを COGNITO_USER_POOLS 型のオーソライザーとして登録します。API Gateway のドキュメントでは、メソッドの認可スコープを空にすると 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認可の使い分けと設定例にまとめました。
ALB配下のアプリによるx-amzn-oidc-dataの署名検証
コンテナや EC2 の Web アプリを ALB で受けている構成では、リスナールールに認証アクションを置けます。未認証のアクセスは ALB がログイン画面へ送り、認証後はユーザー情報を x-amzn-oidc-* ヘッダーに載せてターゲットへ渡します。ALB のドキュメントによると、署名されるのは 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で認証を後付けする手順:リスナールール設定とヘッダー検証で解説しています。
既存システムからユーザーを移行する
移行の方式は3つあります。CSV インポートのドキュメントによると、既定のインポートはパスワード以外の属性だけを移し、ユーザーは 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と管理者リセット・ロックアウト設計、移行後のサインイン監査はCognitoのログ取得手順:CloudTrailとユーザーアクティビティログでサインインを監査するにまとめています。
Cognitoを採用する条件と見送る場面
Cognito が向くのは、システムが AWS 上にあり、API Gateway や ALB の前段で認証を済ませる構成です。認証の要件がサインアップ、サインイン、ソーシャルログイン、MFA、パスキーの範囲に収まり、直接サインインまたはソーシャル連携の利用がアカウントまたは AWS Organizations 単位の無料枠10,000 MAUに収まれば、その区分の Essentials の MAU 料金はかかりません。SAML・OIDC連携の無料枠は別に50 MAUで、SMS送信などの関連料金も別途確認します。フロントエンドを Amplify で作る場合はAmplify×Cognito認証の実装手順:defineAuthと既存プールの参照が続きの手順です。
見送るべき場面もはっきりしています。認証の途中に独自の同意画面を挟む、外部 API の結果で分岐する、といった要件はマネージドログインの枠に収まらず、カスタム認証チャレンジの Lambda トリガーが積み上がります。CUSTOM_AUTH を使うなら、SRP などほかの認証フローを有効のまま残すとそれを迂回されるので、クライアントの ExplicitAuthFlows を絞ります。SAML・OIDC 連携が主体で利用者が数百人規模の社内システムは、50 MAU を超えた分が課金対象になるため、ほかの IdP と費用を比べます。オンプレミスを含めて認証を1か所に集めたい場合は、自前でホストできる Keycloak が候補です(Keycloakとは?Oktaとの違いと連携手順|Auth0・Cognito比較で導入を判断【2026年10月】)。
既存の会員基盤からの移行や、複数システムをまたぐ認証の設計まで含めて検討する場合は、認証基盤・ID管理システム開発で要件整理から構築まで支援しています。
よくある質問
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 を分けたい場合はマネージドログイン経由の認可コードフローを使います。