こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。
今回は以前作成したAPI Gateway + Lambda環境にCognitoを連携します。
こうすることで認証無くしてAPIを叩けないようにします。
環境イメージ
動作フロー
① クライアント → Cognito : ユーザー認証リクエスト
クライアントは、Cognitoユーザープールに対して認証リクエストを送信します。この際、ユーザー名とパスワードを使用して認証を行います。
- 認証フロー:
- Cognitoは、ユーザー名とパスワードを受け取り、認証を試みます。
- 認証フローには、USER_PASSWORD_AUTHやUSER_SRP_AUTHなどのオプションがあります。
② Cognito → クライアント : トークン発行
認証が成功すると、Cognitoは以下のトークンをクライアントに返します。
- IDトークン: ユーザーの身元情報(例: ユーザー名、メールアドレスなど)を含む。
- アクセス トークン: APIへのアクセス権を制御するために使用。
- リフレッシュ トークン: トークンの有効期限が切れた際に新しいトークンを取得するために使用。
③ クライアント → API Gateway : IDトークンを付けてAPIリクエスト
クライアントは、Cognitoから取得したIDトークンを使用してAPI Gatewayにリクエストを送信します。
- リクエスト例:
GET /protected-resource HTTP/1.1
Host: api.example.com
Authorization: Bearer - AuthorizationヘッダーにIDトークンを設定することで、認証済みのリクエストとして扱われます。
④ API Gateway → Cognito : トークンの検証
API Gatewayは、Cognitoユーザープールをオーソライザーとして設定しています。このため、受け取ったIDトークンの有効性をCognitoに確認します。
- 検証内容:
- トークンの署名が正しいか。
- トークンの発行元(Issuer)が正しいか。
- トークンの有効期限が切れていないか。
⑤ Cognito → API Gateway : トークンの判定
Cognitoがトークンを検証し、結果をAPI Gatewayに返します。
- 有効な場合:
- API Gatewayはリクエストをバックエンド(Lambdaなど)に転送します。
- 無効な場合:
- API Gatewayは「401 Unauthorized」エラーをクライアントに返します。
⑥ API Gateway → Lambda : ユーザー情報付きでリクエスト転送
トークンが有効である場合、API GatewayはリクエストをLambdaに転送します。この際、トークン内のユーザー情報(例: sub, email, cognito:groupsなど)を含めます。
- ユーザー情報の利用例:
- Lambdaでユーザーのグループ情報を参照し、アクセス権を制御する。
⑦ Lambda → API Gateway : APIレスポンスを返す
Lambdaはリクエストを処理し、結果をAPI Gatewayに返します。
- レスポンス例:
- 成功時: HTTPステータスコード200とデータ。
- エラー時: 適切なエラーメッセージとステータスコード。
⑧ API Gateway → クライアント : HTTPレスポンスを返す
API Gatewayは、Lambdaから受け取ったレスポンスをクライアントに返します。
- クライアント側の動作:
- レスポンスを受け取り、画面表示やデータ処理を行います。
構築
Cognitoでユーザプールを作成する
API GatewayなのでSPA用のもので良い。
リターンURLはAPIにアクセスするためのものを選択。
作成出来るとこんな感じの画面が表示される。今回の要件ではこれは使わない。
左のユーザプールを押下する。
作成したユーザプール名を押下して、左にあるアプリケーションクライアントを押下して、
編集ボタンを押下すると以下のような画面になる。
"ユーザー名とパスワード (ALLOW_USER_PASSWORD_AUTH) を使用してサインインします"にチェックを付けて変更を保存を押下する。
変更が反映されていることを確認する。
認証フローのところにユーザー名とパスワードと表示されていることを確認する。
ユーザを作成する。
画面左のユーザ管理からユーザを押下すると以下のような画面が表示され、ここで作成することが出来る。
作成されたことを確認する。
確認ステータスを確認済みに変更するために対応を続ける
AWSマネコンからCloudShellを起動して以下のようなコマンドを実行する
aws cognito-idp admin-set-user-password \
--region ap-northeast-1 \
--user-pool-id ap-northeast-1_pDwtyMhPe \
--username ohtsuka-shota \
--password P@ssword123 \
--permanent
解説
aws cognito-idp admin-set-user-password \
--region <リージョン> \
--user-pool-id <ユーザプールID> \
--username <ユーザプールに登録したユーザ名> \
--password <ユーザプールに登録したユーザのパスワード> \
--permanent
確認ステータスが確認済みになっていることを確認する。
API Gatewayの管理画面からオーソライザータブを押下する。
オーソライザーを作成を押下する
名前は任意のもの
オーソライザーのタイプはCognitoを選択して、ユーザプールは先ほど作成したものを。
トークンのソースはAuthorizationを入力。
作成出来たことを確認する。
このオーソライザが機能しているか確認するため、Cognitoからトークンを取得する。
CloudShellで以下を入力。IdTokenの値を控える。
$ curl -X POST https://cognito-idp.ap-northeast-1.amazonaws.com \
> -H "Content-Type: application/x-amz-json-1.1" \
> -H "X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth" \
> -d '{
> "AuthFlow": "USER_PASSWORD_AUTH",
> "ClientId": "6eb4ji0thnlms9ipijauo7fk5s",
> "AuthParameters": {
> "USERNAME": "ohtsuka-shota",
> "PASSWORD": "P@ssword123"
> }
> }' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4137 100 3951 100 186 12054 567 --:--:-- --:--:-- --:--:-- 12612
{
"AuthenticationResult": {
"AccessToken": "eyJraWQiOiI0Y09zbUUxY1o4Q1dManM2WUZ4MitZcTRtbUVTZWRkRzc3TFRtQyt0YlNrPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI3NzA0MWFmOC1jMGQxLTcwYWEtYWQzZS1jZGEwYmVhZmE3NDgiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuYXAtbm9ydGhlYXN0LTEuYW1hem9uYXdzLmNvbVwvYXAtbm9ydGhlYXN0LTFfcER3dHlNaFBlIiwiY2xpZW50X2lkIjoiNmViNGppMHRobmxtczlpcGlqYXVvN2ZrNXMiLCJvcmlnaW5fanRpIjoiM2M0YzQyYzktZDhjMC00M2VjLTg2ZjEtMTllNDZhNGY3MTRjIiwiZXZlbnRfaWQiOiJhY2Y3MWI2MC0yZWU5LTQ2MTEtYWNlOC1kYzU2NmU3MTEzNTciLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIiwiYXV0aF90aW1lIjoxNzQ4NjYzNjI2LCJleHAiOjE3NDg2NjcyMjYsImlhdCI6MTc0ODY2MzYyNiwianRpIjoiNDQ5NWJiMzItMDIxMi00NDRiLWExYjYtNzczOGZkOTRkYmIzIiwidXNlcm5hbWUiOiJvaHRzdWthLXNob3RhIn0.CicH6mld8TGMLzumUE043RjqKjMEpiH6KWE3I1HH02yIZaG0iKaUlCRMEs_-udO3bioGhx4vsOrBT3o8bObeFsJlsjRtGskJs1rhPzw1w31bWYCK1eM15i47q2PSuS4SlpsnjCly14xCAKC5P86UrnoYNi2ktWsYUtzSakjz6SCJl1cbc5Ai_A2IKbI6dArHFHmAnStW0a5NXkYHQEw7e23v3KWSmapUEFJSPz-FwkCzQY3QakffDyOwORd3AaXcjyoc-NecF0C2-QrjIrrJYcsEhy4pLGxpEAi9dRx2DPJK_FgQ1Izfn41GJez3DjROK9WyZ-_ftBUTtu20cgbgHA",
"ExpiresIn": 3600,
"IdToken": "eyJraWQiOiJyYjd2VnAwU0VoemRuQWZ1YnlOeFZXSEZzU3FFS0VBSUMzWWo5eVE4b0Z3PSIsImFsZyI6IlJTMjU2In0.eyJvcmlnaW5fanRpIjoiM2M0YzQyYzktZDhjMC00M2VjLTg2ZjEtMTllNDZhNGY3MTRjIiwic3ViIjoiNzcwNDFhZjgtYzBkMS03MGFhLWFkM2UtY2RhMGJlYWZhNzQ4IiwiYXVkIjoiNmViNGppMHRobmxtczlpcGlqYXVvN2ZrNXMiLCJldmVudF9pZCI6ImFjZjcxYjYwLTJlZTktNDYxMS1hY2U4LWRjNTY2ZTcxMTM1NyIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNzQ4NjYzNjI2LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuYXAtbm9ydGhlYXN0LTEuYW1hem9uYXdzLmNvbVwvYXAtbm9ydGhlYXN0LTFfcER3dHlNaFBlIiwiY29nbml0bzp1c2VybmFtZSI6Im9odHN1a2Etc2hvdGEiLCJleHAiOjE3NDg2NjcyMjYsImlhdCI6MTc0ODY2MzYyNiwianRpIjoiMzViYzlmMWEtZjVjZC00YWRkLTkwYzEtNzAwZWIxNzUzZWYwIn0.xZLWGTtuDobbETp4xuumgHjfsCfHC4UH3E1stiUw-L7s4j3VsKr8Ahcx78o4Qoak19jo-6DOTbspftaiGhNTCXm9qFAJcsz843NApa_IK16bB8v5AyXMs6P6n69cfj7NJG9Px-QWHM3ijbCS298EQG4dcSP-ZpIhH-WZAxWplZVOKWSaF_6beOxF8OoWABSQwie-684slWo53ymRVZsYVr_a4csKheBc7NQ9DtIIyvLZLUqY49zn5hhPC4Z6Otl8SwYAqgfw7KDY5cF8OPcSosnF_AbY09U82gQVUfEB3UBFrRcx0LqAwpyWTFmWuLv2vC-Ff7A-0txI_dDvjVEVXA",
"RefreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.lpm_Ogn9Go7q0bIINHzh5GD3GqyQrfbMVGEqv8cqWA522PquiMzrmAgB-NBLhIESVSS7K_zQVCkDUZofZ38dav21utJomIsSDRoYMbi3hx_avQeCRhjwrmEiLtGC5Nx3mu-cArn88-p6c6129znYPJFDYsAuK9E7VGVHHaJYV1pItFC3fOmcJ7m5-ctY5clxidGCRHI_qR17MKBqeDs8fPZezx0f0PYzJT6yGLK3mQPfZ1GSXfR0Oq7CfVPWG55LJ9Uj4qQ_Kh-FSr0msIEZLwIGH0ajtDW7HWEtNIwtPPqGzHDmOhhpaf8yImp1zpdZXiJD3oolBoBpDCbaBvB5PA.zYVs35iKqANbTZKn.9JM3ku8m8hgczmpY1J4Gt_XyGXHr_PLViQAtnLqBbXhuXWVTznXVwMqEMTrxRbQzzBclMYfylXGLdNy17E6lWxYh25Fbuz1m9zZAtODugsCNFJ30Eto3kAxJ_hhJUrYADXhn7FYgRiJIR6g1RSr2J8PK7Wx5VnkEh0uLQAY89L7Iwx-sXkr0Jpj7jITN6M2opzabd0LIkiygzyL84yIdFzFTLXSdF4FitVeQeRXAdPVO7ym-e1FYSi9iGB_3dIrdBzMvP-NvNbafEteJ2MtkS6Nq5ACSTDWBJLLy0wjwxnS0oqwpVkpjYVoUwYicaV0PkOqtn80UaJrdM6mJEHouc22ysIOmDRlf36g_8irUUYQ3yTCqkO53tb-NtgxLSt0Uze-JihPFK5h70kiIjEY3yl24a-wI-fx_3pBpcVVVxCpuHwZKsZ-dzeCl8gZ2bT7fdXB5cXEtgk2dMcy_PbYypcd-nl6xDsiZVR-Rd7XBiwwRNEYgYioYJ4p-8MlPuyhu1UJHGYpfCKKhYlVVG1WJwwGXn2S9R_oAc4KUuxYzYDcv76uA1crqUq61cZhQEyqtB7byUx0yyJAE4Hsw9ug11B8WQLDY7Y-vYoCFfyeQ9gKFyAu6BMLuAWqlj0L-JM_cO77VljMRpY3RUIr2O10vkD6MCFpO3_-h-ngH0yQaLpT0T-hzSsLhz-RtuOTFdhKwezufA6YO5Jbw39ZLy0BJp0lvcI9vYVBtswV11iJYumyUpu-dhHG5UXaCOaFHs2p9mckOTXVk-Jcecq45ePAUik0eVCzAPj1wdY6_RdXSmZdpKubtgrWrTEMai1Kxi0zGZkmIn0DdWkVq-m7wyAClfNx1Gsl0enlNDY5crPoMZIJBet6h0xJrKMaPRZ-BN5C9JZWI1NdP2pWcsFspQRvJhGbqKVj9v_b75OhOzdxLMQty3Ept8VpRzyxAn6NMSHieqlqkGuqVr4bEGRkg5JUnp0f4yZAGqp-7hbUnAKO5oueohEmRD6w7sq1BYtJ4rQvoHLmxQ2AEE9UGcin71qvN_ueqBQIP5yoLLlf_APnAXH8cGA60yhbrg-anDXR9lLzxHziGykNLlK7bwTcLCAxbkpYUKBudlrLWok-w1uu_QNZ7gp10_cecm_xlr1X-58n4q_KFbxqiS2S7z8PzbuNoSM14RCwgXlkpgmPwUON8f5_0qjV8qnP2WiI4_uiT-e1hwSM-xzU9WiR7VwJm6Y_VPQTVSfpRCOJpl-AwbhcZboMz4ZmUI09gF6OVtaR5yrmdyTM9WSd4jr3k5qkhDijMd-aFx6Ah14zemZaz.46V8CStWHyKkoi_ZWGbiWg",
"TokenType": "Bearer"
},
"ChallengeParameters": {}
}
解説
curl -X POST https://cognito-idp.ap-northeast-1.amazonaws.com \
> -H "Content-Type: application/x-amz-json-1.1" \
> -H "X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth" \
> -d '{
> "AuthFlow": "USER_PASSWORD_AUTH",
> "ClientId": "<ユーザプールID> ",
> "AuthParameters": {
> "USERNAME": " <ユーザプールに登録したユーザ名>",
> "PASSWORD": "<ユーザプールに登録したユーザのパスワード>"
> }
> }' | jq
作成出来たオーソライザー名を選択すると、テストを行うことが出来る。
先程取得したIdTokenの値をトークンの値欄に入力してオーソライザのテストを押下
200が返ってくれば、APIからCognitoへの認証が上手くいっていることになります。
APIにオーソライザを紐づけます。
リソースからメソッドリクエストを押下し、編集をクリック。
認可に先ほど作成したオーソライザを選択して保存
認可の欄にオーソライザの名前が表示されていることを確認する。
その後APIをデプロイ。
試験
CognitoとAPI+Lambdaが連携できていることを確認する。
まず、前の通りcurlしてみる。
結果として、{"message":"Unauthorized"}と出ていることから認証が働いている。
$ curl -X POST https://hc620f2cy5.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"Unauthorized"}
先程取得したIdTokenを渡した上で再度実行する
{"statusCode": 200, "body": "{"message": "Hello AWS API Gateway!"}"}~と出力されており、
認証が通り、APIの裏側のLambdaが実行されていることがわかる。
curl -X POST https://hc620f2cy5.execute-api.ap-northeast-1.amazonaws.com/dev/hello -H "Authorization: Bearer eyJraWQiOiJyYjd2VnAwU0VoemRuQWZ1YnlOeFZXSEZzU3FFS0VBSUMzWWo5eVE4b0Z3PSIsImFsZyI6IlJTMjU2In0.eyJvcmlnaW5fanRpIjoiM2M0YzQyYzktZDhjMC00M2VjLTg2ZjEtMTllNDZhNGY3MTRjIiwic3ViIjoiNzcwNDFhZjgtYzBkMS03MGFhLWFkM2UtY2RhMGJlYWZhNzQ4IiwiYXVkIjoiNmViNGppMHRobmxtczlpcGlqYXVvN2ZrNXMiLCJldmVudF9pZCI6ImFjZjcxYjYwLTJlZTktNDYxMS1hY2U4LWRjNTY2ZTcxMTM1NyIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNzQ4NjYzNjI2LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuYXAtbm9ydGhlYXN0LTEuYW1hem9uYXdzLmNvbVwvYXAtbm9ydGhlYXN0LTFfcER3dHlNaFBlIiwiY29nbml0bzp1c2VybmFtZSI6Im9odHN1a2Etc2hvdGEiLCJleHAiOjE3NDg2NjcyMjYsImlhdCI6MTc0ODY2MzYyNiwianRpIjoiMzViYzlmMWEtZjVjZC00YWRkLTkwYzEtNzAwZWIxNzUzZWYwIn0.xZLWGTtuDobbETp4xuumgHjfsCfHC4UH3E1stiUw-L7s4j3VsKr8Ahcx78o4Qoak19jo-6DOTbspftaiGhNTCXm9qFAJcsz843NApa_IK16bB8v5AyXMs6P6n69cfj7NJG9Px-QWHM3ijbCS298EQG4dcSP-ZpIhH-WZAxWplZVOKWSaF_6beOxF8OoWABSQwie-684slWo53ymRVZsYVr_a4csKheBc7NQ9DtIIyvLZLUqY49zn5hhPC4Z6Otl8SwYAqgfw7KDY5cF8OPcSosnF_AbY09U82gQVUfEB3UBFrRcx0LqAwpyWTFmWuLv2vC-Ff7A-0txI_dDvjVEVXA"
{"statusCode": 200, "body": "{\"message\": \"Hello AWS API Gateway!\"}"}~