背景
AWS使ってサーバーレスで自分用の家計簿的なwebサービスを勉強も兼ねて開発中。大分実用に耐えるようになってきた所。
前回の投稿
自分しか使わない、かつ自分しかそのアドレス(CloudFrontが自動生成した不規則文字列)は知らないけど、外部公開はされてるので、不正アクセスを防ぐためにも認証機構をつけたい。Cognito使いたい。
参考にさせてもらったページ
Cognitoが提供する認証機能を手を動かしながら理解
CloudFormation で Cognito
AWS Cognitoを使ってみた
vue-cliで作成したSPAにシンプルにCognitoログインを組み込む
Cognitoのユーザプールでの認証するところまでを最速でやる
まずは理解
AWS Cognitoがログイン認証とそれに関わるAWSリソース制御が可能という事は知ってるけど、実際にどのようなイメージでそれを制御しているのかを知らない。まず、AWSコンソールでCognitoのページに行く。
2つの機能
「ユーザープールの管理」「IDプール」の二つが現れる。追加画面を開いて、何が出来るか確認していく。
ユーザープール
AWSコンソールメッセージによると、アプリユーザーのサインアップとサインインオプションを提供するユーザーディレクトリです だそうで。左側に以下のタブが存在。
- 属性:ユーザーに関する情報(名前とかE-mailアドレスとか)の設定。カスタムも設定可能。
- ポリシー:パスワードのポリシー関係の設定
- MFAそして確認:多要素認証を使うか、パスワードリセット方法などの設定。これに関係して、SMSを使用してメールを送信する為のIAMロールをCognitoに設定する必要あり。
- メッセージのカスタマイズ:認証に使用するメールの送信元、Reply-toやメッセージ中身の設定
- タグ:多くのユーザープールを管理する時に使うのかな?
- デバイス:デバイスの情報を記憶するか。新デバイスでアクセスした時に警告メールを送ったりする為に必要?後は、一度ログインしたデバイスはしばらく再認証しないとか?
- アプリクライアント:このユーザープールを使うアプリの追加。それ用のキーが発行される。その認証方法も細かく設定できるらしい。
- トリガー:ユーザー認証の各タイミングをトリガーにしてLambda関数を実行できるらしい。
IDプール
同じく、ユーザーに別の AWS のサービスへのアクセスを許可する AWS 認証情報を提供します だそうで。こちらはタブ形式でなく、ウィザード形式での設定のみ。
- ID プールを作成する:基本情報。名前や、認証プロバイダーの選択。Cognitoを始めとしてFacebookやtwitterも使える。
- アクセス権限の設定:Cognitoが使用するIAMロールの設定。認可された場合のロールと、認可されてない場合のロールの二つが必要。
理解中間まとめ
- Cognitoのユーザープールは、ユーザーの管理に関わる機能を提供。認証プロバイダーの一つ。
- CognitoのIDプールは、認証プロバイダーを使って、AWSの機能認可を行う。
- 例1:認証してる場合、S3の特定共通リソースアクセス許可
- 例2:S3のユーザー毎に異なる特定リソース許可
- 例3:ユーザー毎に、DynamoDBでユーザー毎に異なるキーの値のデータを許可
- その他、AWS機能に関する制限が可能
- CognitoのIDプールで使用する認証プロバイダーにCognitoのユーザープールを使用可能。代わりにFacebookとかtwitterの認証プロバイダーも使用できるが、その場合、当然ながら認証機構側でその機構を使用する為のIDなどを事前に取得しておく必要がある。
決めるべき事
- 認証プロバイダーをユーザープールにするか外部認証プロバイダーにするか
- ユーザープールにする場合、どのような認証をするか
- ユーザープールを使うアプリクライアントがどの様な認証フローを必要とするか
- 認証された場合とそうでない場合にAWSリソースに対してどのようなアクセス制限を行うか
今回のケースでの方針
- 外部認証プロバイダーのIDとか持ってないので、ユーザープールを認証プロバイダーに使う
- 目的はログイン機構だけなので、認証詳細はデフォルトでOK。以下部分を変える。
- e-mailをユーザーIDにする
- 管理者のみユーザー追加可能(他の人に使われるのを防ぐ目的だから)
- アプリクライアントの詳細設定。JavaScript(ブラウザ)では「クライアントシークレットを生成」にチェックすると使えないらしいので外す。ブラウザアプリでは「ALLOW_USER_SRP_AUTH」をチェックすればよいらしい。
- 今回はログイン制御だけ出来ればいいので(AWSリソースの制限は無い)、IDプールのロールはデフォルトのまま。
実際の作成ステップ
ユーザープール
- ユーザープールの作成
- 名前を入れる(今回は、MyAppUserPool)
- デフォルトを確認する
- エイリアス属性 タブにて、「E メールアドレスおよび電話番号」をチェック。e-mailをIDとして使うようにする
- ポリシー タブにて、「管理者のみにユーザーの作成を許可する」をチェック。
- アプリクライアント タブにて「アプリクライアントの追加」。
- アプリ名前を指定(今回は、MyHomeAccount)
- ブラウザアプリなので、必須チェック以外外す
- アプリクライアントの作成 を行う
- 確認 タブにて、「プールの作成」を行う。
- 左上リンクから、Cognito-ユーザープールトップに戻る。
- 作ったユーザープールをクリックして開く。
- 全般設定 タブにて、プール ID をメモ
- アプリクライアント タブにて、アプリクライアントIDをメモ
IDプール
- 新しいIDプールの作成 をクリック
- 名前を入れる(今回は、MyAppIDPool)
- 認証プロバイダー を展開し、Cognitoタブにて、ユーザープール作成時にメモしたユーザープールID、アプリクライアントIDを入力
- プールの作成 を行う。
- ロール指定の画面が出てくる。一旦そのまま作成。
- 左上リンクからフェデレーティッドアイデンティティを選択し、IDプール一覧を表示
- 作成したIDプールを選択
- 右上のIDプールの編集を選択
- 認証されていないロール、認証されたロール それぞれで、新しいロールの作成を選択&デフォルトのまま許可
- 変更の保存
ユーザー作成
管理者のみユーザー作成出来る設定なので、ユーザープールの管理画面でユーザー作成。AWSコンソールで、ユーザープール => 作成したユーザープール => ユーザーとグループ にて仮パスワードでユーザー作成。
Vue.jsでの画面を作成
- vue-cliで作成したSPAにシンプルにCognitoログインを組み込む をトレース。
- ステップ1で使用している cognito.js を一部修正。cognitoUser.authenticateUser に newPasswordRequired 処理を追加 ※1
- 実際にログイン。新しいパスワード入れる。※2
※1:ログインすると、以下のエラーがブラウザコンソール出てきたので
Uncaught (in promise) TypeError: callback.newPasswordRequired is not a function
変更部分詳細
/**
* username, passwordでログイン
*/
login (username, password) {
const userData = { Username: username, Pool: this.userPool }
const cognitoUser = new CognitoUser(userData)
const authenticationData = { Username: username, Password: password }
const authenticationDetails = new AuthenticationDetails(authenticationData)
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
// 実際にはクレデンシャルなどをここで取得する(今回は省略)
resolve(result)
},
onFailure: (err) => {
reject(err)
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
var newPass = window.prompt('新しいパスワードを入力してください', '入力値は表示されます。別の人に見られていない事を確認してください')
cognitoUser.completeNewPasswordChallenge(newPass, {}, this)
}
})
})
}
※2:新しいパスワード入れた後、以下のエラーがブラウザコンソール出てきた。completeNewPasswordChallenge 関数のcallbackもちゃんと実装必要があるはずだが、今回はスルー。Failしたと思われるが、ユーザーのステータスをAWSコンソールで確認するとCONFIRMEDになっていたし。今後ここらへんもうちょっとちゃんとすることにする。
Uncaught (in promise) TypeError: callback.onFailure is not a function
(2019年12月14日追記)
ちゃんとしました 。callbacksを雑にしていた事が原因でした。詳細はリンク先の別投稿で。
結論的な手順まとめ
- AWS Cognitoを使ってみた を参考に、CognitoのユーザープールとIDプールを作成。
- vue-cliで作成したSPAにシンプルにCognitoログインを組み込む を参考に、ステップ1で作成したIDを使ってログイン画面実装。
※ただし、自分の目的に合わせて微調整は必要。今回の投稿が微調整の参考になれば幸いです。
思った事
- もし、複数のユーザー毎に異なるS3領域やデータを保存する場合、S3のフォルダ構成やDynamoDBのキー構造も考慮する必要が出てくる。今回のケースは複数ユーザーは考慮しないが、今後そのようなものを設計する場合は、Cognitoでの制御方法を考慮に入れる事が必要そう。
- Vueに基本機能として認証制御が存在していた。考えれば当然だが素晴らしい。
- Cognitoとそれ用のjavaScriptライブラリで簡単に認証機能つけられた。素晴らしい。
- 今回の件だけなら、Cognitoユーザープールだけで十分だったかも。