AWS Cognito の認証情報を API Gateway + Lambda で受け取りたい


Cognitoの認証情報をLambdaで受け取る為の覚え書き。

やりたいこと

  1. Cognito(User Pool)でログイン
  2. ログインしたユーザーでAPIを叩く
  3. 叩かれたAPIから実行されるLambda関数の中で、認証情報を取得したい。すごくしたい。

API Gateway でログインを要求する設定は可能なので、(非ユーザーがそのAPIを叩いてもエラーが返される様になる)そのログインしているユーザーをLambdaに伝える事が出来ればOKですね。

1. API Gateway でユーザーログインを要求する様にする

オーソライザーのセットアップ

  1. ユーザープールを作成しておく
  2. API Gateway のコンソールで「オーソライザー」を選択
  3. 「作成」から「Cognito ユーザープールオーソライザー」を選択
  4. 認証に用いるユーザープールを選択して「作成」する

メソッドのセットアップ

  1. 任意のリソースの任意のメソッドを選択(/hello の GET 等)
  2. 「メソッドリクエスト」を選択
  3. 「認証の設定」の「認証」で、認証に用いるユーザープールを選択

これで、このAPIはユーザープールでのログインを要求するようになり、未ログインユーザーは利用出来なくなります。

2. ログインユーザーでAPIを呼ぶ

JavaScript SDK でログインしてAPIを呼んでみましょう。

// 既にログイン処理を済ませた前提

userPool.getCurrentUser().getSession(function(e, session){
    $.ajax({
        type: "GET",
        url: "http://xxxxx.execute-api.xx-xxxxx-1.amazonaws.com/sandbox/hello",
        headers: {
            Authorization: session.getIdToken().jwtToken
        }
    })
    .done(function(res){
        console.log(res);
    });
});

Authorization ヘッダーで idToken を渡してやれば通ります。

Cognito は、この idToken を localStorage に保存して継続的に認証をしていて、idToken は中に認証情報を内包しています。要するに、API Gateway および Lambda でリクエストヘッダーを取得してこれをデコードしてやれば、認証情報を取得出来るわけですね。

3. API Gateway でリクエストヘッダをLambdaにパススルーする

  1. 任意のリソースの任意のメソッドで、「統合リクエスト」を選択
  2. 「統合タイプ」で「Lambda関数」を選択して、LambdaリージョンとLambda関数を入力する
  3. 「本文マッピングテンプレート」で Content-Type を新規作成(application/json)
  4. 「テンプレートの生成」で「メソッドリクエストのパススルー」を選択
  5. 「保存」

何をしているのかというと、API Gateway に来たリクエストのパラメータ類をLambdaにそのままパスする様に設定しています。こうすることで、Lambda関数の引数 event でこれらを取得する事ができます。

4. Lambda 関数でパラメータを取得する

exports.handler = function(event, context, callback){
    var token = event.params.header.Authorization;
    ...
};

event から辿ってゆけば idToken を取得する事ができます。でもこのままでは意味不明な文字の羅列なのでデコードしてやらなければなりません。

デコードは、Cognito JavaScript SDK も依存していた、sjcl.js を使用します。関数と一緒にzipで固めてアップロードするのが正式なのでしょうが、面倒だったので取り急ぎ丸ごとエリアにコピペしました。

ドキュメント通りにビルドしてやるのを忘れないようにしましょう。

sjcl.js の導入をした上で、次のようにデコードします。

exports.handler = function(event, context, callback) {    
    var token = event.params.header.Authorization,
        payload = token.split(".")[1],
        data = JSON.parse(sjcl.codec.utf8String.fromBits(sjcl.codec.base64url.toBits(payload)));

    callback(null, {
        "hello": "world",
        "user": data["cognito:username"]
    });
};

idToken の正体は、JSON文字列をなんやかんやエンコードした物でした。デコードの結果得られたオブジェクトから認証情報が取得出来るようになっています。これでLambdaさんが無事認証情報を知る事ができました。やったー!

デコードのコードは、SDKから拝借しています。

cf) https://github.com/aws/amazon-cognito-identity-js/blob/master/src/CognitoIdToken.js#L41

めんどくさい

もっと楽な方法がある気がしてなりません。