プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Cognito ユーザープールを単独で API GateWay と共に使う

前に書いた記事

Cognito ユーザープール使ってみました - プログラマーのメモ書き

では、Cognito ユーザープールを Cognito Identity Pool (Federated Identity) と一緒に使うようなことを書きました。

でも、よくよく考えてみると外部IDプロバイダで認証したIDを使う必要がないなら、Cognito ユーザープールのみを使って、AWSのサービスへは API Gateway 経由でアクセスするのが手っ取り早いのではないかと思い立ちました。

そこで、今回は、Cognito ユーザープールを単独で用いて、ユーザー認証後 API Gateway 経由で Lambda 関数を呼び出す方法を試してみました。 以下は、作業時のメモになります。

なお、Cognito ユーザープールの利用については前回の記事をベースにしているので、詳しくはそちらをご覧ください。

ユーザープールの作成~ログインまで

この部分は、前回の記事と同じなので、割愛します。 クライアント側のログイン処理とマイページの処理が少し変わるので、あとで説明したいと思います。

Lambda 関数の定義

ごく簡単な内容の Lambda関数を定義しておきます(Node.js で定義しています)。 処理内容は以下の通りです。

exports.handler = (event, context, callback) => {
    // TODO implement
    context.done(null, {message: "Hello API:" + event['email']});
};

event['email'] の部分ですが、次節で述べるように API Gateway で認証した情報を eventとして渡すことができ、それを出力しています。

API Gateway の定義

次に API Gateway を定義します。

  1. APIの作成を押します f:id:junichim:20170924152442p:plain
  2. API 名を適当につけます。 f:id:junichim:20170924152600p:plain
  3. 追加したAPIの『リソース』の『アクション』より『メソッドの作成』を選択し、GET を追加します。 f:id:junichim:20170924152811p:plain
  4. 『統合タイプ』でLambda関数を選択し、『Lambdaリージョン』で先ほどLamda関数を作成したリージョンを選択し、Lambda関数名も入力して、保存を押します。 f:id:junichim:20170924153045p:plain
  5. 『Lambda関数に権限を追加する』という確認ダイアログが出てくるので、OKを押します f:id:junichim:20170924153142p:plain
  6. 『アクション』より『CORSの有効化』を選択します
  7. 特に設定を変更する必要がなければデフォルト設定のまま『CORSを有効にして既存のCORSヘッダーを置換』ボタンを押します f:id:junichim:20170924153427p:plain
  8. 『アクション』より『APIのデプロイ』を選択して、この時点で一度デプロイしておきます。デプロイされるステージが未定義の場合は『新しいステージ』を選択し、ステージ名を適当に入れます(ここではprodとしました) f:id:junichim:20170924153704p:plain なお、 ステージエディターが立ち上がりますが、今回は特に設定を変更しません。

これで、 API Gateway が作成できました。

オーソライザー

次に、作成した API Gateway に対して、Cognito ユーザープールを使って、認可(認証に基づく認可)を得るために、オーソライザーを定義します。

  1. 作成したAPIを選択し、『オーソライザー』を選択します f:id:junichim:20170924154227p:plain
  2. 『新しいオーソライザーの作成』ボタンを押します
  3. 『名前』を適当に入力し、『タイプ』として『Cognito』を選択します f:id:junichim:20170924154249p:plain
  4. 『Cognitoユーザープール』として、作成済みのユーザープール名を入力します
  5. 『トークンのソース』として『Authorization』を入力します
  6. 『作成』をクリックします

これでオーソライザーが作成できました

メソッドの認可の設定

作成したオーソライザーを認可に使うため、メソッドの設定を変更します

  1. 『リソース』のGETメソッドを選択します f:id:junichim:20170924154834p:plain
  2. 『メソッドリクエスト』をクリックして、編集画面を開きます
  3. 『認証』(英文だと、Authorizationなので、本当は認可?)の編集アイコン(鉛筆のようなやつ)をクリックして、ドロップダウンリストより、作成したオーソライザーを選択します f:id:junichim:20170924154857p:plain なお、このとき、オーソライザーを作成済みにも関わらず、ドロップダウンリストに現れない場合は、ページをリロードすると表示されるようになりました(もっといい方法があれば教えてください)

本文マッピングテンプレート

最後に、Lambda関数を呼び出したユーザー(API Gatewayを呼び出したユーザー)の情報を伝えるための設定を行います

  1. 『リソース』でGETメソッドを選択します
  2. 『統合リクエスト』をクリックして編集状態にします f:id:junichim:20170924200946p:plain
  3. 『本文マッピングテンプレート』をクリックします f:id:junichim:20170924201108p:plain
  4. 『マッピングテンプレートの追加』をクリックして、Content-Typeとして application/json を入力します f:id:junichim:20170924201226p:plain
  5. パススルー動作の変更を行う旨の確認ダイアログが表示されるので、デフォルトの『はい、この統合を保護します』を選択します f:id:junichim:20170924201345p:plain
  6. テンプレートを定義します(内容は下記参照)
  7. 最後に『保存』ボタンを押せば、完了です

テンプレートの内容は、下記としました。

{
  "email": "$context.authorizer.claims.email",
  "sub" : "$context.authorizer.claims.sub"
}

テンプレートに指定できる項目については、

API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway

などを参照してください。

なお、本文マッピングテンプレートの『リクエスト本文のパススルー』オプションに関するの詳しい説明については、下記記事がわかりやすかったです。

dev.classmethod.jp

クライアント側の処理

前回記事のクライアント側の処理と大きくは変わらないのですが、ログイン画面で承認された後の動作を若干変更し、マイページにAPI Gateway 呼び出し用のボタンと結果の表示を行い処理を追加します。

ログイン

今回は、 Cognito ユーザープール単独で用いる(Cognito Identity Pool を用いない)ので、ログイン後、Identity Pool と統合する処理が不要になります。 具体的には、

upsample.login = function() {
    var username = $('#inputUserName').val();
    var password = $('#inputPassword').val();
    if (!username | !password) { return false; }

    var authenticationData = {
        Username: username,
        Password: password
    };
    var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);

    var userData = {
        Username: username,
        Pool: upsample.UserPool
    };

    var message_text;
    var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function(result) {
            console.log('access token + ' + result.getAccessToken().getJwtToken());

            $(location).attr('href', 'mypage.html');
        },

        onFailure: function(err) {
            alert(err);
        }
    });

}

となります。 また、htmlファイルにおいても、AWS SDK を読み込む必要がなくなっています。

マイページでのAPI Gateway の呼び出し

マイページのhtmlファイルで API Gateway を呼び出すためのボタンを追加しておきます。

ボタンが押された際に、API Gateway へアクセスして、その結果を表示します。

upsample.apiCalling = function() {

    var cognitoUser = upsample.UserPool.getCurrentUser();
    if (cognitoUser != null) {
        cognitoUser.getSession(function (err, sessionResult) {
            if (sessionResult) {
                var idToken = sessionResult.getIdToken().getJwtToken();

                $.ajax(
                    "https://API Gateway のURL",
                    {
                        type: 'GET',
                        contentType: 'application/json',
                        headers: {
                            Authorization: idToken
                        },
                        async: false,
                        cache: false
                    }
                )
                .done(function(data) {
                    $('#api_result').text('result: ' + JSON.stringify(data));
                })
                .fail(function() {
                    console.log("failed to call api");
                });
            }
        });
    }
}

呼び出し時は、 Authorization ヘッダーに Idトークンを指定すればOKです。 認証が切れた状態だと、ステータスコード 401 でレスポンスが返ってきます。

実験

では、実際にブラウザからアクセスしてみます。 ログイン画面を表示後、ログインするとマイページが表示されます。

f:id:junichim:20170924204848p:plain

ここで、『API実行』ボタンを押すと、 API Gateway 経由で Lambda が呼ばれ、ログイン時のユーザーのメールアドレスを含んだメッセージが返されます。

f:id:junichim:20170924204752p:plain

問題なく表示されていますね。

コンソールから試し

必要というわけでもないのですが、curlでも試してみます。

ログインを行っていない状態で

curl --include https://xxxx.execute-api.ap-northeast-1.amazonaws.com/ステージ名

とすると 401 が返ってきます。

次にブラウザでログイン後、ディベロッパーツールでIdトークンの値を取得して、curlで呼び出すと

curl --include https://xxxx.execute-api.ap-northeast-1.amazonaws.com/ステージ名 -H 'Authorization: Idトークンの値'

問題がなければ、

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Date: Sat, 23 Sep 2017 14:04:50 GMT
x-amzn-RequestId: 29d9022d-a068-11e7-a3a1-ad50f52c7d3e
Access-Control-Allow-Origin: *
X-Amzn-Trace-Id: sampled=0;root=1-59c66a01-cdeca4ab25f31bc912c89815
X-Cache: Miss from cloudfront
Via: 1.1 e20489ede5d5a153bd489790cc8e71ab.cloudfront.net (CloudFront)
X-Amz-Cf-Id: awZjLU21X6F0SBrmnSQlezZPMap4Mxv2Arvoxg6TLYy5yLVFfggsbA==

{"message":"Hello API:test@example.com"}

のようなヘッダが返ってきます。 やはり、問題なさそうですね。

はまったところ

実は、最初、 API Gateway のドキュメント

Amazon Cognito ユーザープールをオーソライザーとして使用して REST API へのアクセスを制御する - Amazon API Gateway

を参考にして、API Gateway のオーソライザーの設定でトークンのソースとして

method.request.header.Authorization

としていたのですが、このままでは 401 が返ってきてAPI Gateway にアクセスできません。

単に、

Authorization

のみでよかったのでした。 でも、これがわかるまで、半日ぐらいかかってしまったので、一応書いておきます。

参考

以下の記事を参考にさせていただきました。

Cognito User Poolsの機能と使い所 - たれぱんのびぼーろく

http://www.h4a.jp/detail/25148

API GatewayでCognito UserPools Authorizerを使う - Qiita

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

また、今回の一連のソースは Github にアップしてあるので、気になる方はご参考までどうぞ。

github.com

ディズニーのくるくる回るおもちゃの電池交換

ずいぶん前に子供をディズニーのイベントに連れてってた時に、欲しい!と言われて買った、くるくる光って回るおもちゃですが、電池が切れました。

f:id:junichim:20170923220721j:plain

これ、裏側のフタを空ければ電池交換できるんですが、これが開けにくいのってなんの。

必要なもの

  • マイナスドライバー(少し大きめのほうがいいかも)
  • 交換用電池(単4 3本)

開け方

  1. 電池フタの下部の隙間にマイナスドライバーを入れます。 f:id:junichim:20170924145346j:plain
  2. ドライバーを隙間の奥に押し込みながら、おもちゃの端(黒いところ)側を支点にして、てこの要領で少し前側(電池が入っている部分側)に押します
  3. 力をよしなに調整すると、フタが、パカッと取れます

力のかけ具合が難しいところですが、少しづつ力を入れていくとうまくできると思います。 (動画撮ればよかったですね、すいません)

ちなみに電池ケース上部にある、ネジは無関係のようで、外さなくてもフタ取れます。

フタがとれた後はこんな感じです。

f:id:junichim:20170923221135j:plain

フタ(表)

f:id:junichim:20170923221157j:plain

フタ(裏)

f:id:junichim:20170923221213j:plain

フタの画像を見ると分かるように、フタの下側(画像で下側)部分のツメで引っ掛けている感じです。

取り付けるときも逆の要領でつけます(ドライバーを使ったほうがスムーズに取り付けられると思います)。

最悪、ツメが折れたりしても責任取れませんので、自己責任でお願いします。

小さいお子さんのいる方、怪我しないように、頑張って電池交換にチャレンジしてみてください。

参考

hirokiman.way-nifty.com

独自ドメインで、メールとhttps化したWebサイトを別サーバーで運用する (2/2)

blog.mori-soft.com

の続きです。 ここから、https でアクセス可能なWebサイトの公開のための作業になります。

Amazon Certificate Manager によるSSL証明書の取得

ドメイン所有者宛てに送られるメールを受け取れるようになったので、SSL証明書の申請を行います。 SSL証明書は、リージョン毎に作成されるようです。

ただし、CloudFront で使用できる証明書は us-east-1 のリージョンのもののみなので、このリージョンに対して証明書の申請をすることに注意してください。

詳しくは、

よくある質問 - AWS Certificate Manager | AWS

にある『Q: 複数の AWS リージョンで同じ証明書を使用できますか?』に記載されています。

AWSのコンソールにログインして、Certificate Manager を開きます。 リージョンとして、us-east-1 (米国東部(バージニア北部))を選択します。 『証明書のリクエスト』を押します。

f:id:junichim:20170915102906p:plain

ドメイン名を入力する欄があるので、今回使用する独自ドメイン名を入力します(Zone Apexとして入力します)。 ワイルドカード証明書も取ることができるので、『この証明書に別の名前を追加』ボタンを押して、サブドメインのワイルドカード証明書も申請しておきます(今回の目的には不要なのですが、別件で使うかもしれないので)。

f:id:junichim:20170915103130p:plain

ちなみに、ワイルドカード証明書だけでは、Zone Apex はカバーされないのでご注意ください。

次に進むと確認画面が表示されます。

f:id:junichim:20170915103547p:plain

ここで、『確定とリクエスト』を押すと、ドメイン所有者に対してメールによる確認が行われます。

f:id:junichim:20170915104222p:plain

メール記載のリンクをクリックして、表示される確認内容に問題がなければ、承認します。 承認後、コンソールの画面に戻り『続行』と押すと、問題なければ、証明書一覧の画面が表示され、ステータスが『発行済み』となります。

f:id:junichim:20170915104848p:plain

こんな感じになってれば、証明書が発行できてます。

S3のホスティング

さて、いよいよホスティングするS3の設定をします。

まずは、バケットを作成します。S3で独自ドメインを割り当てる場合は、サブドメインでもZone Apexでもバケット名をそのドメインの形式にする必要があります。

例えば、独自ドメイン名が example.com なら、バケット名も example.com になります。

また、S3でホスティングする場合、誰でも見れる必要があるため、アクセス許可(アクセスコントロールリストやバケットポリシー)を追加しますが、今回は、CloudFront経由でのアクセスのみに制限するので、現時点では、アクセス許可はデフォルトのままとします(パブリックアクセス許可を与えません)。

なので、この時点ではブラウザで確認できないのでご注意ください。

表示するためのファイルをindex.htmlとしてアップロードしておきます。

Hello WOrld!

公開するバケットの『プロパティ』『Static website hosting』を選択して

f:id:junichim:20170915110643p:plain

設定画面で、

f:id:junichim:20170915110934p:plain

『このバケットを使用してウェブサイトをホストする』を選んでおきます。

CloudFront の設定

S3の設定ができたら、CloudFrontでディストリビューションを作成します。

設定内容としては、httpsでのアクセス(httpはhttpsにリダイレクト)とし、必ずCloudFront経由でS3にアクセスするようにしています。

デフォルトの設定値と異なる箇所を以下に示します。

  • Origin Domain Name : S3のバケット名を選択
  • Restrict Bucket Access : Yes
  • Origin Access Identity : Create a New Identity
  • Grant Read Permissions on Bucket : Yes, Update Bucket Policy
  • Viewer Protocol Policy : Redirect HTTP to HTTPS
  • Alternate Domain Names : 独自ドメイン名
  • Default Root Object : index.html

なぜか、この時点で SSL Certificate として Custom SSL Certificate を選択できなかったので、一旦このままディストリビューションを作成します。

なお、CloudFront経由でS3にアクセスする方法については

blog.mori-soft.com

に記載があるので、ご参考にしてください。

Route53 の設定

独自ドメインに対して Alias レコードを設定します。

f:id:junichim:20170915111507p:plain

レコードタイプとして Aレコードを選択して、AliasをYesにします。

Alias Target として、CloudFront のアドレスを選択します。

CloudFront で証明書を指定

最後に、CloudFrontのディストリビューションを再度開き、『General』タブで『Edit』を行います。

f:id:junichim:20170915111932p:plain

SSL Certificate として Custom SSL Certificate を選択し、その下のドロップダウンリストからACMで取得した証明書を選択します。

これで設定完了です。

確認

さて、ブラウザでアクセスしてみましょう。

f:id:junichim:20170915112319p:plain

httpsでアクセスしてもちゃんと表示されています。 ちなみに、http//独自ドメイン/ でアクセスしても表示されます。

さいごに

設定は非常に楽しかったのですが、実際の料金がどれぐらいになるかはちょっと気になるところです。

Route53, CloudFront, S3

でそれぞれ費用が発生するので、アクセス数次第では結構いいお値段になるかもしれません(まあ、そうなったらうれしくもあるんですが)。

これについては、載せたい内容が固まって、本格的にリリースできてから改めて検証したいと思います。

参考

メールの部分以外はこちらを参考にしました。

qiita.com