前回からまただいぶ空いてしまいましたが、今回はAmazon Cognitoシリーズの4作目です!1~3作目はこちらをご覧ください。
目次
前回のおさらい
前回は、サインイン後(Amazon Cognitoで認証が通った後)、「サインイン中のユーザー情報を取得する」ということをやってみました。ユーザ情報を取得したい場面といえば、例えば、
- 画面上に「ようこそ!◯◯さん」を表示する
- サインインしたユーザーにひもづくデータをDBから取得する
などが挙げられます。
前回は1をJavaScriptを使って実装してみましたが、今回は2について、Amazon API Gateway+AWS Lambdaで実装してみたいと思います。このAmazon Cognitoシリーズでは「サーバーレス」が大きなキーワードなんですが、今回はサーバーレス感がぷんぷんしてていい感じですね…!
アーキテクチャ
アーキテクチャと呼べるほど立派なものではないんですが、今回目指す動作について、簡単に整理しておきます。
矢印もだいぶ楽して書いちゃいましたが、ざっくり構成はこんなイメージになります。
Amazon Cognitoで認証する部分は実装済みなので置いておいて、今回実装したいのは図中の下段の方です。一言で言うと「Web APIをつくる」ということなのですが、AWSのサービスとしては、Amazon API Gateway、AWS Lambdaを使って実現したいと思います。また、Lambdaの先にあるDBについては、とりあえずここではAmazon DynamoDBを置きましたが、今回の記事ではLambdaからのDBアクセスの詳細には触れず、Lambdaでどうやってサインイン中のユーザー情報を取得するか?に焦点を当てたいと思います。ユーザー情報さえ取得できれば、それにひもづくデータをDBから取得するのは簡単なはず!
JavaScriptの実装
そもそもCognitoの認証情報をどうやってLambdaで取得するのかというと、前々回の「Amazon Cognitoを使ったサインイン画面をつくってみる~アクティベーション&サインイン編~」で少し解説した、Cognito認証に成功すると発行されるトークンを使います。
発行されるトークンは3つあり、それぞれIDトークン、アクセストークン、更新トークンといいます。どんなものか少しおさらいすると、
- ID トークンには、name、email、phone_number といった、認証されたユーザーの ID に関するクレームが含まれます。
- アクセストークンは、認証されたリソースへのアクセスを付与します。
- 更新トークンには、新しい ID またはアクセストークンの取得に必要な情報が含まれます。
ですね。今回はこの3つのトークンのうち、IDトークンを使います。各トークンはローカルストレージに保持されているので、JavaScriptで取得することが可能です。
ではまず、JavaScriptを以下のように実装します。(基本的には前回と同じなので、変更部分のみ抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
cognitoUser.getSession(function(e, session){ $.ajax({ contentType : "application/json", headers: { "Authorization": session.getIdToken().jwtToken }, dataType : "json", type : "GET", url : "https://xxxxx.execute-api.yyyyy.amazonaws.com/stage/miso-cognito-sample", success : function(data) { $("div#menu h1").text(data); }, error : function(data) { console.log("error", data); } }) }) |
Ajaxを使って、Web APIを呼び出します(2~16行目)。呼び出す際に、「Authorization」ヘッダーでIDトークンを渡してあげます(4~6行目)。ここで渡したIDトークンをLambdaで受け取り、デコードして認証情報を取得する、という流れになります。
Web APIの呼び出しが成功した場合はsuccess関数(10~12行目)、失敗した場合はerror関数(13~15行目)が実行されます。success関数では、Web APIからのレスポンスをそのまま画面に表示しています。レスポンスの期待値は「ようこそ!◯◯さん」です。うまくいけば、前回と同じ画面が表示されるはず!
API Gatewayの設定
リソースの作成
次に、API Gatewayを設定しましょう。まずはリソースを作成します。リソース名とリソースパスを入力して、「リソースの作成」ボタンをクリックします。
メソッドの作成
リソースを作成したら、次にメソッドを作成します。今回はGETメソッドで作成します。設定項目は以下のように設定し、保存します。
- 統合タイプ: Lambda関数
- Lambdaプロキシ統合の使用: 選択
- Lambdaリージョン: 使用するリージョン
- Lambda関数: 呼び出すLambda関数を指定
- デフォルトタイムアウトの使用: 選択(デフォルト)
オーソライザーの作成
メソッドが作成できたら、次にオーソライザーを作成します。必須設定項目を以下のように設定し、保存します。
- 名前: 任意の名前
- タイプ: Cognito
- Cognitoユーザープール: 対象のユーザープールを指定
- トークンのソース: 「Authorization」
「Cognitoユーザープール」は、このAmazon Cognitoシリーズで使用しているユーザープールを指定します。「トークンのソース」は、JavaScriptの実装に合わせて「Authorization」を設定します。おそらく、使用する箇所すべてで統一されていればこれ以外の名前でも大丈夫だとは思いますが、Cognitoでは「Authorization」が一般的というか、AWSの開発者ガイドでも「Authorization」と書かれています。
メソッドリクエストの設定
先程作成したメソッドの設定から、「メソッドリクエスト」を開き、認可の設定をします。プルダウンで選択肢が表示されるので、選択肢の中から先程作成したオーソライザーを選択します。それ以外の項目はデフォルト設定でOKです。
統合リクエストの設定
次に、同じメソッドの設定から、「統合リクエストの設定」を開き、「Lambdaプロキシ統合の使用」を選択します。(デフォルトで選択済みであればそれでOK)
CORSの有効化
次に、CORSを有効化します。基本的にデフォルト設定でOKですが、「Access-Control-Allow-Origin」は環境に合わせて値を設定してください。CORSが有効化されると、OPTIONメソッドが追加されます。
APIのデプロイ
すべての設定が完了したら、APIをデプロイしてください。これ忘れずに!(私は忘れた)
Lambdaの実装
それではついに、Lambdaを実装しましょう!言語はNode.jsで実装します。Node.jsのバージョンは10.x系にしました。今回は、「ようこそ!◯◯さん」という文字列を返却するようにLambdaを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var jose = require('node-jose'); exports.handler = async (event) => { try{ var token = event.headers["Authorization"]; var sections = token.split('.'); var payload = jose.util.base64url.decode(sections[1]); payload = JSON.parse(payload); // 以下の要領で、ユーザープールの各項目を取得できる console.log("Cognito User (username) : " + payload["cognito:username"]); console.log("Cognito User (family_name) : " + payload["family_name"]); console.log("Cognito User (given_name) : " + payload["given_name"]); const response = { statusCode: 200, headers: { "Access-Control-Allow-Origin" : "*", "Access-Control-Allow-Credentials" : true, "Access-Control-Allow-Headers" : "Origin, X-Requested-With, Content-Type, Accept" }, body: JSON.stringify("ようこそ!" + payload["family_name"] + "さん"), }; return response; } catch (err) { // エラー発生時はエラー文をreturnする console.error(`[Error]: ${JSON.stringify(err)}`); return err; } }; |
3行目のexports.handler~以降が、APIが呼び出されたときに実行される処理です。引数eventを受け取り、リクエストのヘッダー中から「Authorization」ヘッダーの値を取得します(5行目)。
IDトークンはJSON Webトークン(JWT)という形式で、ヘッダー、クレーム(データ本体)、署名がそれぞれ「.」(ピリオド)で連結された文字列です。今回ほしい情報はクレームで、クレームはBase64エンコードされているので、デコードすることによりJSON文字列を取得することができます(6~8行目)。デコードには、node-joseというライブラリを使っています。
取得したIDトークン情報(変数payload)から、ユーザープールで定義している各項目を取得することができます(11~13行目)。あとは、ステータスコードやヘッダー情報とともに、「ようこそ!◯◯さん」(ここで◯◯さんにはIDトークンから取得したfamily_nameが設定される)をレスポンスにセットし、返却してあげればOKです(15~24行目)。
動作確認
それでは動作確認してみましょう!サインインしてみます。メールアドレスとパスワードを入力し、「Sign In」ボタンをクリックします。
すると…
おー!出ましたね。前回と見た目が変わり映えしなくて感動が薄めですが…
果たして、これは本当にLambdaで取得してるのか?という疑問もあると思うので、ちょっとAmazon CloudWatchでログを見てみましょう。Lambdaの実装で、username、family_name、given_nameをコンソールにログ出力しているので、それぞれどんな値が入っているかを確認してみます。
赤く囲った部分がLambdaで出力したログです。usernameの値はマスクしてしまいましたが、family_nameとgiven_nameはそれぞれ「yamazaki」「naoko」が取得できていることがわかります。
おわりに
今回はAPI GatewayとLambdaを使ってCognitoの認証情報を取得してみました。API GatewayとCognitoの連携もすごく簡単ですね。躓くかもしれないのがCORSの設定で、設定自体は一瞬だし、設定内容も合ってるはずなのに、Ajaxでリクエストを投げるとどうもCORSのエラーになっちゃう…という事態が実際にありました。今回ご紹介したように、メソッドや認可の設定がすべて完了後、最後にCORSを有効化する、というのがきっと正しい順序なのだと思います。
今回はすごーく簡単な例でLambdaを実装しましたが、アーキテクチャ図で書いたとおり、実際には取得したユーザー情報をキーにしてDBアクセスするのが一般的なサーバーレスアプリケーションの処理の流れになります。Cognitoユーザープールの定義を考慮しつつDBのテーブル設計をすると、より良い設計ができそうですね!
さて、今回でAmazon Cognitoシリーズは最後(のつもり)です!ただ、サーバーレス活動(?)は今後も続けていく予定ですので、またサーバーレスな話題で記事を書けたらと思っています。Amazon Cognito含め、AWSサービスに興味を持たれた方は、ぜひぜひ触って、便利さを体感してみてくださいね。
執筆者プロフィール
- 社内の開発プロジェクトの技術支援や、新技術の検証に従事しています。主にアプリケーション開発系支援担当で、Java&サーバサイドが得意です。最近は、サーバーレスonAWSを推進しています。