##はじめに
クライアントアプリケーションを作成するにあたって、Cognitoの闇にハマってしまったため、備忘録として学習した内容を残します。
LambdaやSQSなどその他のAWSサービスと同じように公式ドキュメントを読み進めると確実に闇落ちします。(少なくとも私は落ちました。。)
理由として、Oauth 2.0、OpenID Connectの前提知識が必要になり、公式ドキュメントはそれを前提として書かれているからです。
図とスクリンショットを駆使して、概念と作成まで一気に説明します。
初心者にも一撃で理解いただけるよう、各設定内容のユースケースを細かく注釈を入れたので、これだけ見れば大枠は理解できると思います。
アプリケーションの認証認可を行うサービス
アカウント管理・認証認可の付与をAWS側で行ってくれる、フルマネージドサービス
Google・Amazon・Facebookなどの外部IDプロバイダーと連携可能で、Googleのアカウントを使用したログイン機能の作成や多要素認証などの拡張機能を備えています。
###ユースケース
使い道としては、当たり前ですが以下になります。
・アプリでのログイン機能を追加したい場合
・データのアクセス制御を行いた場合
##Cognitoの提供機能
Cognitoはユーザープール・フェデレーティッドアイデンティティ・Cognito Syncの機能を提供しています。
それぞれの機能概要は以下です。
※ちなみに今回はユーザープールのみを使用します。
###ユーザープール
認証・認可を制御するためのメイン機能
Cognitoユーザーの管理・サインインサインアップ機能・外部IDプロバイダーやcognito発行のトークン管理などを行う。
シンプルな認証・認可の仕組みだけ実装したいなら、この機能のみで充足する。
###フェデレーティッドアイデンティティ(別名:IDプール)
ユーザープールのアカウントに対して、IAMロールの付与を行う機能
ここで作成されるアカウントはIDプールと言い、前述のユーザープールとは別物という点に注意
アカウントごとに細かなアクセス制御や、後述のCognito Syncを使用したい場合に使用する。
###Cognito Sync
IDプールで管理されるユーザー単位に、デバイス間でアプリケーションデータを同期する機能
新規で開発する場合、AWS AppSyncという新機能ができたので使用は推奨されない。
##そもそもの話
長々と機能の概要を話しましたが、そもそもの認証・認可の解説をします。
###認証・認可とは
だいたい以下の理解でいいと思います。
用語 | 説明 | 標準化(RFC) |
---|---|---|
認証 | 相手の身元を明らかにすること →ユーザーの認証の仕組み |
OpenID Connect 1.0 |
認可 | 相手に適切な権限を付与すること →認証したユーザーに対するデータアクセス制御の仕組み |
OAuth 2.0 |
###OAuthとは
国際的な認可に関する標準化仕様のこと
クライアントと認可プロバイダーとのやりとり(インターフェイス)を定めている。
処理の大枠についてはこちらを参照してください。
###OpenID Connectとは
国際的な認証に関する標準化仕様のこと
クライアントとOpenIDプロバイダーとのやりとり(インターフェイス)を定めている。
OAuthの仕様を拡張し作られた背景があるため、データフローが似ている。
処理の大枠についてはこちらを参照してください。
###Cognitoの認証・認可について
上記OAuth・OpenID Connectの仕組みを利用して、認可・認証の仕組みを実現しています。
ところどころ出てくる用語は、この仕様上の用語とマッピングされているため、気になる方は調べてみてください。
###トークンについて
先程までの説明でIDトークンとアクセストークンという用語が出てきましたが、
それぞれこのような特性を持ちます。
※更新トークンについては触れてませんでしたが、ここで解説します。
用語 | 説明 |
---|---|
IDトークン | OpenIdにより規定されたユーザー情報 ヘッダー・ペイロード・署名で構成されており、ログインユーザーのクレーム(メールアドレスなど)が含まれる |
アクセストークン | OAuthにより規定されたアクセストークン 通常このトークンを使用して認可の処理を行う |
更新トークン | 各トークンには有効期限が存在する。(長くて1時間とか) そのため、本トークンを使用して、新規トークンの取得を行う(再びユーザー認証を行わなくても、本トークンを用いることで、新規トークンの発行が行える) |
##今回のアーキテクチャー図
フルマネージドサービスのみを使用した、よくあるWebアプリケーションを作っていきます。
ログインのUI画面はCognitoがホストするサービスを使用します。
ユーザーごとの細かな認可制御を行わないため、Cognitoはユーザープールのみ使用します。
※respose_type=tokenに設定し、ApiGatewayのオーソライザーを使用した認可制御を行います。
##Cognitoの設定
それでは早速ユーザープールの作成から行なっていきましょう。
###名前
好きな名前を設定してください。
今回は「ステップに従って設定する」を選択します。
###属性
サインアップ・サインインの設定になります。
→ログインIDをメアドにするかどうかの設定です。
好きな設定を選んでください。
→サインアップ時に、ユーザーに要求する属性に関する設定です。
こちらの情報はIDトークンから取得可能なため、アプリ側で欲しい情報があればチェックを入れましょう。
また、選択肢にない項目はカスタム属性として定義できます。
###ポリシー
パスワードに関する設定です。
→ユーザーに自己サインアップを許可させるかどうかは、自社内限定のサービスの場合は「管理者のみにユーザーの作成を許可する」を設定しましょう。
###MFAそして確認
MFAと確認メッセージの設定です。
→多要素認証は毎回行う場合(必須)と、ログインに指定回数ミスった場合など(省略可能)、利用しない(オフ)から選択可能です。
また、サインアップ時の登録内容確認として、Eメールまたは電話番号の確認メッセージを飛ばすことができます。(どの属性を確認しますか?のチェックボックス)
各送信処理にはIAMロールが必要ですが、デフォルトの指定で問題ありません。
###メッセージのカスタマイズ
メッセージ送信の内容・送信元の設定です。
→SESを使用してメッセージを送る場合は、リージョンの設定が必要です。
※現在(2019/09末)の段階では東京リージョンを選択できないみたいです。
→MFAのメッセージ内容として、確認コードとリンクの2種類から選択可能です。
→Cognitoのユーザーは仮パスワードが発行され、初回ログイン時に変更することでアカウントが有効になります。
###タグ、デバイス、トリガー
タグとデバイス、トリガーの章は省略します。
デバイス設定では、ユーザーのログインした端末を記憶してくれるよう設定できるみたいです。
トリガーの設定では、認証前後にLambdaロジックを挟むことができ、通常の認証フローをカスタムできるそうです。
今回特に使わないので、デフォルトのままで進みます。
###アプリクライアント
Cognitoを利用するアプリケーションに関する設定です。
「アプリクライアントの追加」をクリックし、作成しましょう。
→重要なポイントは以下になります。
・クライアントシークレットの作成有無
シークレットの作成を行う場合は、クラアント側で厳重に管理する必要があります。
例えばクライアントがWebアプリの場合、シークレット漏洩の危険性があるため生成はしないべきです。
・サーバーベースか・アプリベースか
認証の依頼者がエンドユーザー側にあるアプリかバックエンドのサーバーかを選択します。
バックエンドのサーバーの場合、「ADMIN_NO_SRP_AUTH」を選択、
エンドユーザー側にあるアプリの場合、「USER_PASSWORD_AUTH」を選択しましょう。
###確認
設定を見直し、プールの作成をします。
###アプリの統合
もう少し設定をします。
作成したプールの情報から、アプリの統合 > アプリクライアントの設定を選択します。
CognitoがホストするログインUIの設定をします。
→S3のバケット作成と同様、世界中で一意になるのドメインを入力してください。
続いてアプリクライアントの設定をします。
→有効なIDプロバイダを全て選択し、ログイン後のリダイレクト先を入力します。
※まだ、S3のwebページを作成していないため、とりあえずamazonとかのページを指定してみましょう。
OAuthの設定は、トークンを直接取得する「Implicit grant」を選択します。
もし、MVCモデルのようなアプリケーションを構築して入りのであれば、OAuthの理想的な形「Authorization code grant」を選択してください。
※「Authorization code grant」を選択した場合、エンドポイントにはトークンの代わりに認可ーコード(トークンと交換可能な手形)がクエリーパラメータとして渡されます。
エンドユーザーは認証コードをアプリサーバーに送ることで、サーバー側で認証コードとそれを使用して取得したトークンを管理することが実現でき、よりセキュアな設計になります。
「許可されている OAuth スコープ」については、どの情報を参照可能にするかの設定になります。
特にこだわりがなければ全て選択しましょう。
###ユーザーの作成
最後にユーザーを作成します。
今回は「管理者のみにサインアップを許可する」を選択したため、コンソール上でアカウントを作成します。
全般設定 > ユーザーとグループから「ユーザーの作成」をクリックします。
→必要項目を入力したら設定完了です。
##Cognito動作確認
Webブラウザから以下のURLを入力してみましょう。
https://{ドメイン名}.auth.{リージョン名}.amazoncognito.com/login?response_type={レスポンスタイプ※}&client_id={クライアントID}&redirect_uri={リダイレクトURL}
※レスポンスタイプは"token"か"code"を選択します。(今回の設定ならtokenを設定)
ログインに成功するとリダイレクトページに遷移します。
URLを見てみると、以下のようになっていると思います。
https://{リダイレクトURL}#id_token={IDトークン}&access_token={アクセストークン}&expires_in=3600&token_type=Bearer
→あとはJavaScriptから各パラメーターを取得すれば、だいたいのやりたいことができます!
##Lambda・APIGatewayの設定
Lambda側の設定は特にありません。
APIGatewayについては、オーソライザーを使用し認証の機能を付与します。
ささっと作っていきましょう。
###Lambdaの設定
関数の作成 >「一から作成」を選び、好きな関数名と環境を選択後、作成をクリック
「トリガーを追加」をクリックし、APIGatewayを作成します。
以下のように入力し、追加を押します。
これでLambda側の設定は終了です。
※細かい設定は後ほどします。
###APIGatewayの設定
コンソールから、先ほど作成したAPIGatewayの設定画面に飛びます。
※この方法で作成した場合、APIGatewayの名前は「関数名 + -API」という名前になっています。
「新しいオーソライザーの作成」をクリック、以下のように入力しましょう。
※「Cognitoユーザープール」の箇所は先ほど作成した、ユーザープール名を選びましょう。
→「トークンのソース」で指定する名前はAPIGatewayにリクエストを投げる際のヘッダーフィールドに定義されます。
このフィールドにトークン(IDまたはアクセス)を設定することで、APIGatewayがCognito側に認証を自動で行ってくれるようになります。
※ここで指定する名前は好きな名前でいいです。
一般的に「Authorization」が使われるようです。
####オーソライザーの設定
メソッドに対して、オーソライザーを設定します。
リソース > 設定したいメソッド(今回はANY) > メソッドリクエストを選択
認可の箇所を先ほど作成したオーソラーザーを設定しましょう。
※オーソラーザーの選択肢が出ない場合は、一度画面の更新をした方がいいです。
####マッピングテンプレートの設定
リソース > 設定したいメソッド(今回はANY) > 統合リクエストから
「Lambda プロキシ統合の使用」のチェックボックスを外します。
下にスクロールし、「application/json」のテンプレートを作成しましょう。
※ここで設定した値は、Lambdaのevent変数に設定されます。
とりあえずパラメータの設定はしないので、下の画面のよう設定してください。
※APIGatewayとLambdaの設定とパラメーターの受け渡しは、ここが一番わかりやすいので気になる方は見てください。
####CROSの設定
今回はS3のドメインからアクセスを行うため、CROSの設定が必要になります。
アクション > CROSの有効化から、設定をそのままの設定で「ヘッダーを置換」します。
####デプロイ
これらの設定を反映させるには、デプロイが必要になります。
アクション > デプロイから、「デプロイ」をクリックしてください。
※ここで指定したステージ名はエンドポイントのURLの一部になります。
→ここで作成したエンドポイントは以下のようになります。
https://〇〇〇〇.execute-api.{リージョン名}.amazonaws.com/{ステージ名}/{lambda関数名}
##Webページ(呼び出し元)の作成
これまで作成したAWSリソースたちを呼び出すwebページを作成します。
ライブラリはJQueryのみ使います。
###トークン・ユーザー情報の取得
Cognito動作確認で使用したドメイン名を使用し、ログインユーザー情報の取得を行います。
アクセスを行うエンドポイントは以下です。
https://{ドメイン名}.auth.{リージョン名}.amazoncognito.com/oauth2/userInfo
※GETリクエストのみ許容しており、リクエスト時はヘッダー情報にアクセストークンを設定する必要があります。
公式の説明を参考にアクセスを行い、localStrageに必要情報の格納を行います。
####アクセストークン・IDトークンの取得
URLの#(シャープ)以降に付与されているため、JSから取得を行います。
関数はこちらの内容を拝借させていただきました。
//指定パラメータ取得処理(貰い物)
function getParameterByHash(name, url) {
let wHash = window.location.hash; //ハッシュ(#)以降をとる
wHash = wHash.replace('#', '&'); //↓の処理を使いたいがために#を&に変換するorzorz
name = name.replace(/[\[\]]/g, "\\$&");
let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(wHash);
if (!results) return null;
if (!results[2]) return null;
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
####ユーザ情報取得
先ほどの関数を使用して、メインの処理を行うコードは以下になります。
※この関数はログイン後のユーザーが正しく認証できたかチェックを行う関数になります。
<処理の内容>
1.各種トークンの取得
2.トークンの取得に失敗した場合、ログインページに遷移させる。
3.ユーザー情報の取得
4.localStrageに保存
//認証済みか検証を行い、ユーザー情報を取得する(認証未済の場合はログインページにリダイレクトする)
function checkSession() {
//パスパラメータの取得
let idToken = getParameterByHash('id_token');
let accessToken = getParameterByHash('access_token');
//エラーならログインページに遷移
if (idToken == null || accessToken == null) {
window.location.href = 'ログインページのURL';
}
//ユーザー情報取得
const requestAws = axios.create({});
requestAws.get('https://{ドメイン名}.auth.{リージョン名}.amazoncognito.com/oauth2/userInfo', {
headers: {
Authorization: " Bearer " + accessToken
}
})
.then(function(response) {
responseJson = JSON.parse(JSON.stringify(response));
//localStorageに各種情報取得
let storage = localStorage;
storage.setItem("idToken", idToken);
storage.setItem("accessToken", accessToken);
storage.setItem("name", responseJson.data.family_name + ' ' + responseJson.data.given_name);
});
}
###APIGatewayへのアクセス
先ほど作成したAPIGatewayのエンドポイントに向けて、リクエストを投げます。
※オーソライザーを使用しているため、ヘッダーにIDトークンまたはアクセストークンを設定します。
//APIGatewayにリクエスト
function requestGateway() {
let idToken = localStorage.idToken;
let headers = {
headers: {
'Authorization': idToken
}
};
let url = 'APIGatewayのエンドポイント';
const request = axios.create({});
request.get(url, headers)
.then(function(response) {
console.dir("結果:" + response);
});
}
###ユーザー情報表示
せっかくなので先ほど保存した氏名を画面に表示できるようにします。
// ログイン認証完了後画面表示処理
function showView() {
//名前表示
$("#yourName").text(localStorage.name);
}
###Webページ読み込み時に起動する関数
JQueryの関数を使用して、ページ読み込み時に先ほどの関数を呼び出すように設定しましょう。
※ページ読み込み時にこれらの関数を呼ぶことで、正しい認証ユーザーか制御することができます。
//初期処理(ページ読み込み時にロードされる)
$(function() {
console.log("Start");
// 認証チェック
console.log("Start:checkSession");
checkSession();
//ログイン後のUI表示処理
//※ログイン成功時にのみ行いたいページ表示などを本関数で実装すると便利です。
console.log("Start:showView");
showView();
//APIGatewayにリクエスト
console.log("requestGateway");
requestGateway();
});
###HTMLの作成
シンプルでいいです。
とりあえずテンプレ的な内容です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Sample Cognito</title>
<script src="https://unpkg.com/axios/dist/axios.min.js" defer></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="S3のURL/index.js"></script>
</head>
<body>
<h2>ようこそ<span id="yourName"></span></h2>
</body>
</html>
##S3の設定
先ほど作ったhtml・JSファイルを公開バケットにアップロードします。
内容がかぶるので設定はこちらを参考に進めてください。
##微修正
一通りのリソースの準備が整いましたので、微修正を行います。
###Cognitoユーザープールの修正
CognitoのコールバックURLをS3のindex.htmlのURLに修正します。
Cognito > ユーザープール > 作成したユーザープール > アプリクライアントの設定 からコールバックURLの内容を変更しましょう。
###index.htmlの修正
読み込むJSファイルの格納先と認証エラー時に遷移するログインページのURLの内容を書き換えましょう。
##実行
以下のログインURLからアクセスし、ログイン後のページで登録した氏名が表示できれば成功です!
https://{ドメイン名}.auth.{リージョン名}.amazoncognito.com/login?response_type=token&client_id={クライアントID}&redirect_uri={s3のindex.htmlのURL}
###注意点(エラーが起きた場合)
私が少しハマった内容を残しておきます。。。
・各トークンは有効期限が1時間に設定されています。
有効期限切れのトークンを使用した場合は、認証エラーになりますので再ログインしましょう。
・APIGatewayの呼び出しに失敗する場合、大抵はデプロイ忘れです。
APIGatewayの設定が少しでも漏れていると、ブラウザ上ではCROSのエラーと表示されます。
騙されずに設定を見直し、再デプロイを行いましょう。
・APIGatewayのテストでは成功し、webからの呼び出しに失敗する場合、
エンドポイントの指定、ヘッダー情報の指定があっているか見直しましょう。
##まとめ
かなり長い記事になってしまいましたが、概要から作成まで説明しました。
Cognitoは様々な記事があったのですが、図解している記事があまりなかったため、私は苦労しました。
この記事をベースにしていただければ、あとはやりたいことに特化した記事を参考に素晴らしいCognitoライフが過ごせると思います!