HTTP APIのJWTオーソライザーを使ってCognitoユーザープールと連携してみる

2020.03.13

CX事業本部@大阪の岩田です。

API GatewayのHTTP APIがついにGAされました。

[アップデート] より高速に!より低コストに!プライベート統合も可能に!API Gateway の HTTP API が GA されました!

HTTP APIの魅力的な機能の1つとしてJWT オーソライザーが挙げられます。REST APIではCognitoユーザープール以外の認証プロバイダを利用する場合はLambdaオーソライザー(カスタムオーソライザー)が必要でしたが、HTTP APIではLambdaの実装無しにCognitoユーザープール以外の認証プロバイダが利用可能です。

Amazon API Gatewayの新機能「HTTP API」のJWT Authorizersを理解する #reinvent

REST APIではCognitoユーザープールがある意味「特別扱い」されていたのですが、HTTP APIではCognitoユーザープールだろうが他の認証プロバイダだろうが、同じ手順で設定することになります。元々REST API & Cognitoユーザープールで構築していたAPIをHTTP API & JWT オーソライザーに置き換えるにはどのような設定が必要かを実際に試してみました。

やってみる

まずはHTTP APIのバックエンドに設定するLambdaを作成します。

import json

def handler(event, context):
    
    return {
        'statusCode': 200,
        'body': json.dumps(event)
    }

受け取ったイベントをそのまま返却するだけのシンプルなLambdaです。続いてHTTP APIを作成します。作成の手順は先程の丸毛の記事を参考にして下さい。

作成できたらJWT オーソライザーを追加します。

設定画面で以下のように設定していきます

  • 名前:適当な名前 ここではCognitoとしました
  • IDのソース:受け付けたHTTPリクエストのどこからJWTのトークンを取得するか ここでは$request.header.Authorizationとしました。これでHTTPリクエストのAuthorizationヘッダからIDを取得するという意味になります。
  • 発行者URL:認証プロバイダのURLを指定します。いわゆるissuerですね。今回は認証プロバイダにCognitoを利用しているので、https://cognito-idp.<リージョン>.amazonaws.com/<CognitoユーザープールのID>となります。ちなみに今回は別AWSアカウントのCognitoユーザープールを指定しています。こういったことができるのもJWTオーソライザーの魅力ですね。
  • 対象者:日本語だと分かりづらいですが、認証プロバイダ側に登録されているクライアントIDもしくは、JWTのIDトークンのaudに設定されているべき文字列を指定するようです。ここではCognito側で事前作成したクライアントIDを設定しています。

入力できたら「作成」をクリックし、オーソライザーの作成を完了します。

続いて作成したオーソライザーをAPIのパスと紐付けます。先取作成したオーソライザーCognitoを選択して「オーソライザーをアタッチ」をクリックします。

これで準備完了です。curlコマンドから試してみます。

$  curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com
{"message":"Unauthorized"}

AuthorizationヘッダにCognitoから取得したITトークンをセットして試してみましょう

curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com -H "Authorization: <IDトークン>"
{"version": "2.0", "routeKey": "GET /"...

成功です。今度はアクセストークンで試してみましょう。

$ curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com -H "Authorization: <アクセストークン>"
{"version": "2.0", "routeKey": "GET /"...

アクセストークンでも問題なくアクセス出来ていますね。

ペイロードの比較

せっかくなので、REST API & Cognitoユーザープールを利用した場合とLambdaに渡るペイロードがどう変わるかを比較してみました。

REST API & Cognitoユーザープールの場合

{
  "resource": "/",
  "path": "/",
  "httpMethod": "GET",
  "headers": {
    "accept": "*/*",
    "Authorization": "xxx.yyy.zzz",
    "Host": "xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com",
    "User-Agent": "curl/7.54.0",
    "X-Amzn-Trace-Id": "Root=1-5e6ad08b-199037ff7d251e5b2f69c7e0",
    "X-Forwarded-For": "126.83.27.201",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "accept": [
      "*/*"
    ],
    "Authorization": [
      "xxx.yyy.zzz"
    ],
    "Host": [
      "xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com"
    ],
    "User-Agent": [
      "curl/7.54.0"
    ],
    "X-Amzn-Trace-Id": [
      "Root=1-5e6ad08b-199037ff7d251e5b2f69c7e0"
    ],
    "X-Forwarded-For": [
      "126.83.27.201"
    ],
    "X-Forwarded-Port": [
      "443"
    ],
    "X-Forwarded-Proto": [
      "https"
    ]
  },
  "queryStringParameters": null,
  "multiValueQueryStringParameters": null,
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "otwrhi0b2m",
    "authorizer": {
      "claims": {
        "sub": "e24f4709-ef19-4600-bb81-c82e0e04e190",
        "aud": "xxxxxx",
        "email_verified": "true",
        "event_id": "2120a9a1-73f9-402c-b740-af5bc6e02124",
        "token_use": "id",
        "auth_time": "1583923508",
        "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxx",
        "cognito:username": "cognito user",
        "exp": "Fri Mar 13 00:34:32 UTC 2020",
        "iat": "Thu Mar 12 23:34:32 UTC 2020",
        "email": "cognitouser@example.com"
      }
    },
    "resourcePath": "/",
    "httpMethod": "GET",
    "extendedRequestId": "JTWFvEJZNjMFs0A=",
    "requestTime": "13/Mar/2020:00:15:07 +0000",
    "path": "/dev/",
    "accountId": "123456789012",
    "protocol": "HTTP/1.1",
    "stage": "dev",
    "domainPrefix": "xxxxxxxxx",
    "requestTimeEpoch": 1584058507024,
    "requestId": "566f4b58-28e5-43b4-a533-ece246845dbb",
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "sourceIp": "126.83.27.201",
      "principalOrgId": null,
      "accessKey": null,
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "curl/7.54.0",
      "user": null
    },
    "domainName": "xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com",
    "apiId": "xxxxxxxxx"
  },
  "body": null,
  "isBase64Encoded": false
}

HTTP API & ペイロード形式ver1.0の場合

{
  "version": "1.0",
  "resource": "/",
  "path": "/",
  "httpMethod": "GET",
  "headers": {
    "Content-Length": "0",
    "Host": "xxxxxx.execute-api.ap-northeast-1.amazonaws.com",
    "User-Agent": "curl/7.54.0",
    "X-Amzn-Trace-Id": "Root=1-5e6adfec-2a597790cb595ea6cf4aad30",
    "X-Forwarded-For": "126.83.27.201",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https",
    "accept": "*/*",
    "authorization": "xxx.yyy.zzz"
  },
  "multiValueHeaders": {
    "Content-Length": [
      "0"
    ],
    "Host": [
      "xxxxxx.execute-api.ap-northeast-1.amazonaws.com"
    ],
    "User-Agent": [
      "curl/7.54.0"
    ],
    "X-Amzn-Trace-Id": [
      "Root=1-5e6adfec-2a597790cb595ea6cf4aad30"
    ],
    "X-Forwarded-For": [
      "126.83.27.201"
    ],
    "X-Forwarded-Port": [
      "443"
    ],
    "X-Forwarded-Proto": [
      "https"
    ],
    "accept": [
      "*/*"
    ],
    "authorization": [
      "xxx.yyy.zzz"
    ]
  },
  "queryStringParameters": null,
  "multiValueQueryStringParameters": null,
  "requestContext": {
    "accountId": "123456789012",
    "apiId": "xxxxxx",
    "authorizer": {
      "claims": {
        "aud": "xxxxxx",
        "auth_time": "1583923508",
        "cognito:username": "cognito user",
        "email": "hoge@example.com",
        "email_verified": "true",
        "event_id": "2120a9a1-73f9-402c-b740-af5bc6e02124",
        "exp": "1584063280",
        "iat": "1584059680",
        "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxx",
        "sub": "e24f4709-ef19-4600-bb81-c82e0e04e190",
        "token_use": "id"
      },
      "scopes": null
    },
    "domainName": "xxxxxx.execute-api.ap-northeast-1.amazonaws.com",
    "domainPrefix": "xxxxxx",
    "extendedRequestId": "JTftCjgTNjMEMLg=",
    "httpMethod": "GET",
    "identity": {
      "accessKey": null,
      "accountId": null,
      "caller": null,
      "cognitoAuthenticationProvider": null,
      "cognitoAuthenticationType": null,
      "cognitoIdentityId": null,
      "cognitoIdentityPoolId": null,
      "principalOrgId": null,
      "sourceIp": "126.83.27.201",
      "user": null,
      "userAgent": "curl/7.54.0",
      "userArn": null
    },
    "path": "/",
    "protocol": "HTTP/1.1",
    "requestId": "JTftCjgTNjMEMLg=",
    "requestTime": "13/Mar/2020:01:20:44 +0000",
    "requestTimeEpoch": 1584062444905,
    "resourceId": null,
    "resourcePath": "/",
    "stage": "$default"
  },
  "pathParameters": null,
  "stageVariables": null,
  "body": null,
  "isBase64Encoded": true
}

HTTP API & ペイロード形式ver2.0の場合

{
  "version": "2.0",
  "routeKey": "GET /",
  "rawPath": "/",
  "rawQueryString": "",
  "headers": {
    "accept": "*/*",
    "authorization": "xxx.yyy.zzz",
    "content-length": "0",
    "host": "xxxxxx.execute-api.ap-northeast-1.amazonaws.com",
    "user-agent": "curl/7.54.0",
    "x-amzn-trace-id": "Root=1-5e6adfd5-ba27db60d21bceb2f5ac9456",
    "x-forwarded-for": "126.83.27.201",
    "x-forwarded-port": "443",
    "x-forwarded-proto": "https"
  },
  "requestContext": {
    "accountId": "123456789012",
    "apiId": "xxxxxx",
    "authorizer": {
      "jwt": {
        "claims": {
          "aud": "xxxxxx",
          "auth_time": "1583923508",
          "cognito:username": "cognito user",
          "email": "hoge@exmaple.com",
          "email_verified": "true",
          "event_id": "2120a9a1-73f9-402c-b740-af5bc6e02124",
          "exp": "1584063280",
          "iat": "1584059680",
          "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxx",
          "sub": "e24f4709-ef19-4600-bb81-c82e0e04e190",
          "token_use": "id"
        },
        "scopes": null
      }
    },
    "domainName": "xxxxxx.execute-api.ap-northeast-1.amazonaws.com",
    "domainPrefix": "xxxxxx",
    "http": {
      "method": "GET",
      "path": "/",
      "protocol": "HTTP/1.1",
      "sourceIp": "126.83.27.201",
      "userAgent": "curl/7.54.0"
    },
    "requestId": "JTfpUjbKNjMEMCQ=",
    "routeId": null,
    "routeKey": "GET /",
    "stage": "$default",
    "time": "13/Mar/2020:01:20:21 +0000",
    "timeEpoch": 1584062421148
  },
  "isBase64Encoded": true
}

まとめ

HTTP APIがGAされたということで、これからREST API -> HTTP APIの移行を検討される方も多いかと思います。この記事が移行の役に立てば幸いです。