AWS

API Gatewayの認証:Cognito・JWT・Lambdaオーソライザー・IAM認可の使い分けと設定例

API Gatewayの認証:Cognito・JWT・Lambdaオーソライザー・IAM認可の使い分けと設定例

Amazon API Gatewayで「認証付きのAPI」を作る方法は、Cognitoオーソライザー、JWTオーソライザー、Lambdaオーソライザー、IAM認可の4つです。どれが使えるかはREST APIかHTTP APIかで変わり、同じCognitoのトークンでもIDトークンとアクセストークンでは判定の仕方が異なる仕様です。本記事では、4方式の対応範囲を表で整理し、CLIとPythonの設定例で動かすところまで示したうえで、認可キャッシュで別のメソッドが403になる落とし穴と、方式を選ぶ条件を言い切ります。API Gateway自体の役割はAPIゲートウェイの役割と導入判断で整理しています。

まとめ:利用者向けはCognitoかJWT、システム間はIAM、例外だけLambda

ブラウザやスマートフォンアプリから一般利用者が呼ぶAPIなら、REST APIはCognitoオーソライザー、HTTP APIはJWTオーソライザーで足ります。どちらもトークンの署名と有効期限をAPI Gatewayが検証し、無効なリクエストはバックエンドに届く前に401で返ります。社内システムやLambda同士の呼び出しは、IAMロールで署名するIAM認可が手間もコストも小さい方式です。

Lambdaオーソライザーは、Cognito以外のIdPをREST APIで使う、ヘッダーとパスの組み合わせで判定する、といった上の3方式で表せない条件のときに限って選びます。自前で書く分だけ、キャッシュの設計と500エラーの切り分けを背負うからです。どの方式でも、オーソライザーが判定するのは「正しい利用者か」までです。そのユーザーが当該データを読んでよいかは、アプリケーション側で確かめる必要があります。

API Gatewayの認証方式4種と、REST API・HTTP APIごとの対応範囲

最初に、どのAPI種別でどの方式が使えるかを確定させます。ここを間違えると、設定画面に目当ての項目が出てきません。

4方式が受け取るもの・判定する主体・対応API種別を1つの表で比較

REST APIとHTTP APIの比較表(公式)の認可オプションを、クライアントが送るものと合わせて並べると次のとおりです(2026年10月時点)。

方式 REST API HTTP API クライアントが送るもの 向く呼び出し元
Cognitoオーソライザー 対応 JWTオーソライザー経由 CognitoのIDトークンかアクセストークン 一般利用者のWeb・アプリ
JWTオーソライザー 非対応 対応 OIDC・OAuth 2.0のJWT Cognito・Entra ID等の利用者
Lambdaオーソライザー 対応 対応 任意のトークンやヘッダー 独自の判定条件がある場合
IAM認可 対応 対応 SigV4署名付きリクエスト AWS内のシステム間

読み取るべきは2点です。REST APIにはJWTオーソライザーが無く、Cognito以外のIdPのJWTを検証するにはLambdaオーソライザーを書くことになります。逆にHTTP APIには「Cognito」という種別が無く、CognitoのIssuer URLをJWTオーソライザーに登録して使います。

APIキーとリソースポリシーを認証方式に数えないほうがよい理由

設定画面で隣に並ぶAPIキーは、呼び出し量を数えて制限するための識別子です。同じ使用量プランに入った別のAPIまで呼べてしまうため、認可の手段にはなりません。詳細はAPI GatewayのAPIキーと使用量プランの設定で解説しています。

リソースポリシーはREST API限定で、送信元のIPアドレスやVPCエンドポイントで絞る仕組みです。「どこから来たか」は判定できても「誰か」は判定しません。IP制限の組み方はAPI GatewayのIP制限の手順と使い分けにまとめています。どちらも、本記事の4方式に重ねて使う前提の部品です。

CognitoオーソライザーをREST APIのメソッドに付け、IDトークンで呼び出す手順

ユーザープールとアプリクライアントは作成済みとして進めます。まだの場合はCognitoのユーザープール構築からJWT検証までの手順で用意し、同記事にあるcreate-authorizerでオーソライザーを作っておいてください。

update-methodで認証を設定し再デプロイ後にcurlで401と200を確認

オーソライザーは作っただけでは効きません。メソッドのauthorizationTypeとauthorizerIdを書き換え、ステージへ再デプロイして初めて反映されます。Cognitoオーソライザーの公式手順も、変更後の再デプロイを必須としています。

# GET /orders に Cognito オーソライザー(ID: abc123)を付ける
aws apigateway update-method --region ap-northeast-1 \
  --rest-api-id abcde12345 --resource-id r1s2t3 --http-method GET \
  --patch-operations \
    op=replace,path=/authorizationType,value=COGNITO_USER_POOLS \
    op=replace,path=/authorizerId,value=abc123

# 変更をステージに反映する(ここを忘れると認証なしで通り続ける)
aws apigateway create-deployment --region ap-northeast-1 \
  --rest-api-id abcde12345 --stage-name prod

# トークンなしは 401、IDトークン付きは 200 になることを確かめる
URL=https://abcde12345.execute-api.ap-northeast-1.amazonaws.com/prod/orders
curl -s -o /dev/null -w "%{http_code}\n" "$URL"
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: $ID_TOKEN" "$URL"

Cognitoオーソライザーのトークンの読み取り元は、作成時の--identity-sourceで指定したヘッダー(通常はAuthorization)です。1つのオーソライザーに登録できるユーザープールは最大1,000で、この上限は引き上げられません。

スコープを指定するとアクセストークン判定に切り替わる仕様と401の原因

Cognitoオーソライザーは、メソッドに認可スコープを設定したかどうかで、受け取ったトークンの扱いを変えます。スコープが空ならIDトークンとして検証し、スコープを設定するとアクセストークンとして扱い、トークン内のスコープがメソッドのスコープのどれかと一致しなければ401を返します。

# アクセストークンの orders/read スコープを要求する(設定後は再デプロイ)
aws apigateway update-method --region ap-northeast-1 \
  --rest-api-id abcde12345 --resource-id r1s2t3 --http-method GET \
  --patch-operations op=add,path=/authorizationScopes,value=orders/read

この仕様が原因の401は3パターンに分かれます。スコープ未設定のメソッドにアクセストークンを送る、トークン検証欄にaudの正規表現を入れたままアクセストークンを送る(アクセストークンにはaudが無いため拒否される)、コンソールのテスト呼び出しにアクセストークンを使う(テストはIDトークンのみ対応)、の3つです。外部のバックエンドへ委譲する権限を表すならアクセストークンとスコープ、画面に名前を出す程度ならIDトークン、と役割で分けてください。トークンの構造はJWTの構造と署名検証の仕組みで確認できます。

HTTP APIのJWTオーソライザーをCognitoや外部IdPのトークンで設定する手順

HTTP APIでは、IdPの種類を問わずJWTオーソライザーを使います。登録するのは発行者(Issuer)と受け手(Audience)の2つだけです。

IssuerとAudienceを指定してJWT認証をルートに設定するCLIの実行例

CLIではcreate-authorizerにIssuerとAudienceを渡し、続いてupdate-routeで作成したオーソライザーをルートに設定します。

JWTオーソライザーの公式解説によると、Cognitoを使う場合のIssuerはhttps://cognito-idp.<リージョン>.amazonaws.com/<ユーザープールID>の形式です。

# Cognito ユーザープールを発行者、アプリクライアントIDを受け手として登録する
aws apigatewayv2 create-authorizer --region ap-northeast-1 \
  --api-id a1b2c3d4e5 --name cognito-jwt \
  --authorizer-type JWT \
  --identity-source '$request.header.Authorization' \
  --jwt-configuration Audience=1example23456789,Issuer=https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_EXAMPLE

# ルートに付け、アクセストークンの orders/read スコープを必須にする
aws apigatewayv2 update-route --region ap-northeast-1 \
  --api-id a1b2c3d4e5 --route-id xyz789 \
  --authorization-type JWT --authorizer-id def456 \
  --authorization-scopes orders/read

HTTP APIは自動デプロイを有効にしたステージなら再デプロイが要りません。トークンはBearer を付けても付けなくても受け付けられます。CognitoのアクセストークンにはAudienceを表すaudが無くclient_idが入っていますが、JWTオーソライザーはaudが無いときにclient_idを照合するため、アプリクライアントIDをAudienceに登録すれば通ります。

JWTオーソライザーが検証する7クレームと公開鍵キャッシュ最大2時間の影響

JWTオーソライザーは、署名の検証に加えてkid、iss、aud(無ければclient_id)、exp、nbf、iat、scope(またはscp)の7項目を確かめ、1つでも外れれば拒否します。署名アルゴリズムはRSA系のみの対応で、HS256で署名する自前のIdPはそのままでは使えません。

運用で効いてくるのは公開鍵のキャッシュです。API GatewayはIssuerのjwks_uriから取った公開鍵をベストエフォートで最大2時間保持します。IdP側で署名鍵を入れ替えるときは、新旧の鍵がどちらも有効な期間を2時間以上取ってください。呼び出し頻度の低いAPIほどキャッシュが切れやすく、その都度の鍵の取得が初回リクエストの遅延になります。

公式は、IDトークンとアクセストークンを見分ける標準の仕組みが無いことから、IDトークンでの認可が必要な場合を除き、ルートにスコープを必須にするよう推奨しています。スコープを付けないルートでは、同じIssuerとAudienceのIDトークンでも通る点を覚えておいてください。

Lambdaオーソライザーの自前実装でのTOKEN・REQUEST選択とキャッシュ設計

Lambdaオーソライザーは、API Gatewayが呼び出し元の情報を関数に渡し、関数が返したIAMポリシーで許可か拒否かを決める方式です。自由度と引き換えに、設計を誤ったときの症状が分かりにくくなります。

外部IdPのJWTをREST APIで検証するPythonのREQUEST認証実装例

種類はトークン1つを受け取るTOKENと、ヘッダー・クエリ文字列・ステージ変数・コンテキスト変数を組み合わせて受け取るREQUESTの2つです。Lambdaオーソライザーの公式解説は、複数の識別ソースでキャッシュキーを分けられるREQUESTを推奨しています。次の例は、Auth0やEntra IDなど外部IdPのJWTをPyJWTのPyJWKClientで検証します。

import os

import jwt
from jwt import PyJWKClient

# IdP の JWKS エンドポイントから公開鍵を取得・キャッシュする(コールドスタート時に1回生成)
JWKS = PyJWKClient(os.environ["JWKS_URL"])
ISSUER = os.environ["ISSUER"]
AUDIENCE = os.environ["AUDIENCE"]


def lambda_handler(event, context):
    headers = {k.lower(): v for k, v in (event.get("headers") or {}).items()}
    token = headers.get("authorization", "").removeprefix("Bearer ")
    try:
        key = JWKS.get_signing_key_from_jwt(token)
        claims = jwt.decode(token, key.key, algorithms=["RS256"],
                            audience=AUDIENCE, issuer=ISSUER)
    except jwt.PyJWTError:
        raise Exception("Unauthorized")  # API Gateway は 401 を返す

    # methodArn = arn:aws:execute-api:リージョン:アカウント:APIID/ステージ/メソッド/パス
    api_arn, stage = event["methodArn"].split("/")[:2]
    return {
        "principalId": claims["sub"],
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [{
                "Action": "execute-api:Invoke",
                "Effect": "Allow",
                # キャッシュを効かせるため、このステージの全メソッドを許可する
                "Resource": f"{api_arn}/{stage}/*/*",
            }],
        },
        # 値は文字列化されてバックエンドに渡る(オブジェクトや配列は不可)
        "context": {"tenant": str(claims.get("tenant_id", ""))},
    }

関数を作ったら、aws apigateway create-authorizer --type REQUEST --identity-source method.request.header.Authorization --authorizer-result-ttl-in-seconds 300でオーソライザーとして登録し、aws lambda add-permissionでAPI Gatewayからの呼び出しを許可します。この許可が無いと、利用者には500が返ります。

認可キャッシュTTL既定300秒・最大3600秒とmethodArn限定許可の落とし穴

認可キャッシュのTTLはCreateAuthorizerのAPIリファレンスで既定300秒、最大3600秒(1時間)、0で無効と定められています。キャッシュが効くと、同じトークンの2回目以降は関数が呼ばれず、保存済みのポリシーで判定されます。

典型的な失敗は、公式サンプルのとおりポリシーのResourceにevent["methodArn"]だけを入れることです。最初にGET /ordersを呼ぶと「GET /ordersだけ許可」のポリシーがキャッシュされ、同じトークンで続けてGET /usersを呼ぶと、関数が呼ばれないまま403になります。公式も、キャッシュを有効にするならAPI全体のメソッドに通用するポリシーを返し、メソッドごとに変えたいならキャッシュを切るよう書いています。上の例でワイルドカードを返しているのはこのためです。HTTP APIでルートごとにキャッシュを分けたい場合は、識別ソースに$context.routeKeyを足します。

401・403・500の応答とLambdaオーソライザーのエラーログによる原因調査

エラーログには$context.authorizer.errorを使い、応答コードと照合して原因を調べます。

Lambdaオーソライザーのステータスコードは、関数の終わり方で決まります。実務ではまず500を疑うと早く片付きます。

  • 500:関数がUnauthorized以外の例外で終わった、戻り値の形式が不正、API Gatewayに関数の呼び出し権限が無い
  • 401:関数がUnauthorizedで終わった、またはキャッシュ有効時に識別ソースのヘッダーが欠けていて関数自体が呼ばれていない
  • 403:関数がDenyのポリシーを返した、またはキャッシュ済みのポリシーが今回のメソッドを含んでいない

500の原因は、アクセスログの書式に$context.authorizer.errorを加えると記録されます。アクセスログの設定手順はAPI Gatewayのログ設定とX-Rayでの障害調査を参照してください。401なのに関数のログが1行も無い場合は、識別ソースのヘッダー名の綴りを最初に確認します。

IAM認可でSigV4署名を付け、社内システムやLambdaからAPIを呼び出す設定

呼び出し元がAWS上のLambdaやECSタスクなら、トークンを発行する仕組み自体が要りません。呼び出し元のIAMロールでリクエストに署名し、API GatewayがIAMで権限を確かめます。

AWS_IAMを付けたメソッドとexecute-api:Invokeで許可するポリシー例

メソッド側はauthorizationTypeをAWS_IAMにし、再デプロイします(HTTP APIならaws apigatewayv2 update-route --authorization-type AWS_IAM)。呼び出し元のロールには、API呼び出しのIAMポリシー形式に沿ってexecute-api:Invokeを付けます。

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "execute-api:Invoke",
    "Resource": "arn:aws:execute-api:ap-northeast-1:123456789012:abcde12345/prod/GET/orders"
  }]
}

公式は、メソッドにAWS_IAMを設定しなければIAMポリシーは効かず、そのメソッドは公開状態になると注意しています。ポリシーを作っただけで守った気にならないよう、未署名のcurlが403になることを必ず確認してください。

boto3の認証情報とbotocoreのSigV4Authで署名するPython例

署名の方式はAWS Signature Version 4(公式)で、サービス名にはexecute-apiを指定します。Lambda上なら実行ロールの認証情報がそのまま使えます。

import urllib.request

import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest

URL = "https://abcde12345.execute-api.ap-northeast-1.amazonaws.com/prod/orders"

# 実行ロール(またはローカルのプロファイル)の認証情報で署名ヘッダーを付ける
creds = boto3.Session().get_credentials()
req = AWSRequest(method="GET", url=URL)
SigV4Auth(creds, "execute-api", "ap-northeast-1").add_auth(req)

with urllib.request.urlopen(urllib.request.Request(URL, headers=dict(req.headers))) as res:
    print(res.status, res.read().decode())

キーの配布も失効の運用も不要で、権限の付け外しはIAMロールのポリシー変更だけで済みます。一方、ブラウザから直接呼ぶ構成ではAWSの認証情報を利用者に渡すことになるため、この方式は選びません。

API Gatewayの認証方式を選ぶ条件と、オーソライザーを足さないほうがよい場面

ここまでの仕様を踏まえ、方式の選び方を条件で言い切ります。

呼び出し元が人かシステムか、IdPがCognitoかで決める判断の順番

  1. 呼び出し元がAWS上のシステムなら、IAM認可にする。
  2. 人が使う画面からの呼び出しで、IdPがCognitoなら、REST APIはCognitoオーソライザー、HTTP APIはJWTオーソライザーにする。
  3. IdPがEntra IDやAuth0などのOIDC準拠で、RSA署名なら、HTTP APIのJWTオーソライザーにする。REST APIでしか使えない機能(WAFやAPIキー)が要るときだけ、REST APIとLambdaオーソライザーの組み合わせにする。
  4. どれにも当てはまらない独自トークンや、ヘッダーとパスの組み合わせによる判定が必要なら、Lambdaオーソライザーを書く。

上から順に当てはめれば、ほとんどのAPIで選ぶ方式は1〜3のいずれかです。既存の社内IdPとのフェデレーションや、複数システムのID統合まで含めて設計する場合は、認証基盤・ID管理システム開発でご相談いただけます。製品に依存しない方式選定やトークン寿命の決め方はAPI認証の設計と方式選定マトリクスにまとめています。

Lambdaオーソライザーを見送る条件と、データ単位の認可をアプリに残す範囲

CognitoかJWTオーソライザーで済む要件に、Lambdaオーソライザーを選ぶのは過剰です。関数の実行時間とコールドスタートが認証の待ち時間に加わり、JWKSの取得やキャッシュ設計、500の切り分けまで自前で抱えます。「将来の要件に備えて」という理由だけでは選びません。

どの方式を選んでも、オーソライザーに任せられるのは「有効な利用者か」「このメソッドを呼べるロールか」までです。注文IDを書き換えて他人の注文を読む、といったデータ単位の認可は、トークンのsubや受け渡したcontextをバックエンドで照合しなければ防げません。オーソライザーに業務ルールまで詰め込むと、キャッシュとの整合が崩れて403が散発します。認証はゲートウェイ、データ単位の認可はアプリ、と線を引いてください。

よくある質問

API Gatewayの認証設定で、よく出る疑問をまとめました。

CognitoオーソライザーとJWTオーソライザーは何が違いますか?

CognitoオーソライザーはREST API専用で、Cognitoのユーザープールしか登録できません。JWTオーソライザーはHTTP API専用で、Cognitoを含むOIDC・OAuth 2.0準拠のIdPのIssuerを登録できます。挙動の差として、Cognitoオーソライザーはスコープ未設定ならIDトークンとして扱い、JWTオーソライザーはIssuerとAudienceが合えばトークンの種類を区別しません。どちらを使うかは、APIをRESTとHTTPのどちらで作るかで自動的に決まります。

ログインできているのにアクセストークンで呼ぶと401になるのはなぜですか?

REST APIのCognitoオーソライザーで、メソッドに認可スコープを設定していないのが最も多い原因です。スコープ未設定のメソッドは受け取ったトークンをIDトークンとして検証するため、アクセストークンは通りません。アクセストークンで呼ぶならメソッドにスコープを設定して再デプロイし、トークン検証欄にaudの正規表現を入れている場合は外してください。

1つのAPIで認証が必要なルートと不要なルートを分けられますか?

認証が必要なルートと不要なルートは分離可能です。オーソライザーはREST APIならメソッド単位、HTTP APIならルート単位で付け外しするため、ヘルスチェック用のルートだけ認証なしにする構成は普通に組めます。REST APIでCORSの事前確認(OPTIONS)を受けるメソッドにオーソライザーを付けると、ブラウザの事前確認が401で失敗するので外しておきます。認証なしのルートを作るときは、意図せず公開していないかを一覧で確認してください。

オーソライザーが検証した利用者の情報をバックエンドで受け取るには?

REST APIのCognitoオーソライザーは$context.authorizer.claimsでトークンのクレームをマッピングテンプレートに渡せます。HTTP APIのJWTオーソライザーなら、Lambda統合のイベントのrequestContext.authorizer.jwt.claimsに入ります。Lambdaオーソライザーは返したcontextがプロキシ統合のイベントに渡りますが、値はすべて文字列化され、オブジェクトや配列は入れられません。

オーソライザーを付けると追加料金はかかりますか?

API Gatewayの公式料金ページに、オーソライザー単体の課金項目は載っていません(2026年10月時点)。Lambdaオーソライザーは関数の実行としてLambdaの料金が掛かり、認可キャッシュを有効にすれば実行回数を減らせます。Cognitoは利用者数に応じた料金が別途必要です。API Gateway本体の単価差はAPI Gatewayの料金を東京リージョンの実額で計算する記事で比較しています。

関連記事

お気に入りに入れた記事の一覧

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.15 テックブログ リゾートトラスト不正アクセス:会員2.6万件・カハラ横浜3.6万件の対象確認と窓口
  2. 2026.10.16 テックブログ ソースネクスト不正アクセスで最大2,252万件流出|ログインできない理由・パスワード再設定と会員APIの点検
  3. 2026.10.16 テックブログ バーガーキング不正アクセスで約1,118万件の情報漏洩|対象確認・案内メールの真偽と運営側のログ点検
  4. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更の手順
  5. 2026.10.16 テックブログ ワラウ不正アクセスで連携トークン流出|Yahoo! JAPAN ID再連携と対象確認

RELATED POSTS 関連記事

目次