何をするか?
ここでは、API Gateway で提供している REST API にアクセス制御を追加するため、既存の Cognito ユーザープールによるオーソライザーを API Gateway に設定してみます。 これにより、Cognito のユーザープールで認証済みのユーザーのみが REST API を呼び出せるようになります。
後述の CDK コードでは、API Gateway と Lambda 関数、オーソライザーを生成していますが、Cognito ユーザープールは既存のものを参照しています(こういったユースケースは多いと思います)。
なお、CDK による API Gateway の作成方法(Lambda プロキシ統合)については下記の記事を参考にしてください。 ここでは、Cognito ユーザープールによるオーソライザーの作成方法にフォーカスします。
Lambda 関数を作成する
REST API のバックエンドである Lambda 関数は最低限の実装で用意します。 ユーザー認証後に、API Gateway 経由で正しくこのハンドラを呼び出せるかの確認用です。
Cognito ユーザープールによるオーソライザーの追加
下記は、cdk init app
で作成した CDK アプリのスタック生成コードに手を入れたものです。
ポイントは、UserPool.fromUserPoolId()
で既存の Cognito ユーザープールを参照し、それを使って CognitoUserPoolsAuthorizer
オブジェクトを生成する部分です。
作成したオーソライザーは、RestApi
インスタンス生成時に defaultMethodOptions
で設定することができます。
リソース(URL パス)ごとにアクセス制限をかけたいときは、各リソースの addMethod
時にオーソライザーを設定すれば OK です。
これで、REST API へのアクセス時に、Cognito 認証によるアクセス制限がかかるようになります。
defaultCorsPreflightOptions
の設定はちょっと事情が複雑ですが、Web ブラウザ上の JavaScript から認証付き API の呼び出しを行う際に必要になります。
クライアントサイド JS から API を呼び出すときに HTTP リクエストヘッダーに Authorization
情報を付加することになるのですが、この場合は GET 要求の前にプリフライトリクエストという OPTIONS 要求を行うことが HTTP の仕様で決められています。
このプリフライトリクエストはブラウザが自動的に行ってくれるのですが、このリクエストにもクロスドメインでのアクセスを許可しておかないといけません。
この設定を行わないと、CORS エラーが発生して API 呼び出しが失敗します。
デプロイ
Lambda 関数とスタック定義の実装が済んだら、CDK でデプロイします。
$ cdk deploy
最後の方に、次のようにエンドポイント URL が表示されるのでコピーしておきます。
Outputs:
MyapiSampleStack.BookApiEndpointXXXXXXXX =
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
REST API アクセスのテスト
curl
コマンドで REST API の /info
リソースを参照すると、うまくアクセス制限がかかっていることが分かります。
HTTP レスポンスコードは 401 Unauthorized です。
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/info
{"message":"Unauthorized"}
情報を取得するには、Cognito ユーザープールで認証して取得した ID トークンを、REST API リクエスト時の Authorization
ヘッダーで指定する必要があります。
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/info -H "Authorization:XXXX...XXXX"
{"message":"Hello, API Gateway!"}
ID トークンの取得方法
ID トークンは、実際に Web サイト上の UI からサインインしてしまうか、AWS CLI (aws cognito-idp
) などで認証処理を行うことで取得することができます。
下記は、コマンドラインで認証処理を行って ID トークンを取得する例です。 実際には、この前にチャレンジレスポンスに返答する必要があったりしてとても面倒ですが、まぁがんばれば取得できます。
$ aws cognito-idp admin-initiate-auth
--user-pool-id ap-northeast-1_XXXXXXXXX
--client-id XXXXXXXXXXXXXXXXXXXXXXXXX
--auth-flow ADMIN_USER_PASSWORD_AUTH
--auth-parameters USERNAME=username,PASSWORD=password
AuthenticationResult:
AccessToken: eyJraWQiOiJ2aldabmd1meejRSauF5Z43Ez_9LWAxfBP...(省略)
ExpiresIn: 3600
IdToken: eyJraWQiOiJvVU9NU1QyZHhvQVArcnsmwD0WbYqhZppSDVNg...(省略)
RefreshToken: eyJjdHkiOiJKV1QiLC3YjEmAndRqakoOhw4O9al0z1V...(省略)
TokenType: Bearer
ChallengeParameters: {}
上記レスポンスにある、IdToken
の値を、Authorization
ヘッダーでそのまま送れば、REST API は正しいレスポンスを返してくれます。
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/info -H "Authorization:eyJraWQiOiJv..."
{"message":"Hello, API Gateway!"}
送った ID トークンが妥当なものかは、API Gateway が内部で自動で調べてくれるので、Lambda 関数でチェックしたりする必要はありません。
(応用)Web アプリからの REST API 呼び出し
Web アプリからオーソライザー設定された API Gateway を呼び出すには、HTTP リクエスト(fetch
関数呼び出し)時に、Authorization
ヘッダーで Cognito ユーザープールから取得した ID トークンを付加する必要があります。
Web アプリからの Cognito 認証に Amplify ライブラリを使用している場合は、次のような感じで簡単に認証情報付き HTTP リクエストを発行できます。
import { Auth } from 'aws-amplify'
export const fetchWithAuth = async (url: string) => {
const token = (await Auth.currentSession()).getIdToken().getJwtToken()
return fetch(url, { headers: { Authorization: token } }).then((r) => r.json())
}
あとは、このフェッチ関数を通常の fetch
関数の代わりに React カスタムフックなどから呼び出すだけで OK です。
下記は、今回作成した REST API を呼び出す useInfo
カスタムフックの実装例です。