Help us understand the problem. What is going on with this article?

Cognitoを使ってログイン制御してみる

背景

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プールのロールはデフォルトのまま。

実際の作成ステップ

ユーザープール
  1. ユーザープールの作成
  2. 名前を入れる(今回は、MyAppUserPool)
  3. デフォルトを確認する
  4. エイリアス属性 タブにて、「E メールアドレスおよび電話番号」をチェック。e-mailをIDとして使うようにする
  5. ポリシー タブにて、「管理者のみにユーザーの作成を許可する」をチェック。
  6. アプリクライアント タブにて「アプリクライアントの追加」。
  7. アプリ名前を指定(今回は、MyHomeAccount)
  8. ブラウザアプリなので、必須チェック以外外す
  9. アプリクライアントの作成 を行う
  10. 確認 タブにて、「プールの作成」を行う。
  11. 左上リンクから、Cognito-ユーザープールトップに戻る。
  12. 作ったユーザープールをクリックして開く。
  13. 全般設定 タブにて、プール ID をメモ
  14. アプリクライアント タブにて、アプリクライアントIDをメモ
IDプール
  1. 新しいIDプールの作成 をクリック
  2. 名前を入れる(今回は、MyAppIDPool)
  3. 認証プロバイダー を展開し、Cognitoタブにて、ユーザープール作成時にメモしたユーザープールID、アプリクライアントIDを入力
  4. プールの作成 を行う。
  5. ロール指定の画面が出てくる。一旦そのまま作成。
  6. 左上リンクからフェデレーティッドアイデンティティを選択し、IDプール一覧を表示
  7. 作成したIDプールを選択
  8. 右上のIDプールの編集を選択
  9. 認証されていないロール認証されたロール それぞれで、新しいロールの作成を選択&デフォルトのまま許可
  10. 変更の保存
ユーザー作成

管理者のみユーザー作成出来る設定なので、ユーザープールの管理画面でユーザー作成。AWSコンソールで、ユーザープール => 作成したユーザープール => ユーザーとグループ にて仮パスワードでユーザー作成。

Vue.jsでの画面を作成
  1. vue-cliで作成したSPAにシンプルにCognitoログインを組み込む をトレース。
  2. ステップ1で使用している cognito.js を一部修正。cognitoUser.authenticateUser に newPasswordRequired 処理を追加 ※1
  3. 実際にログイン。新しいパスワード入れる。※2

※1:ログインすると、以下のエラーがブラウザコンソール出てきたので

Uncaught (in promise) TypeError: callback.newPasswordRequired is not a function

変更部分詳細

cognito.js
  /**
   * 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を雑にしていた事が原因でした。詳細はリンク先の別投稿で。

結論的な手順まとめ

  1. AWS Cognitoを使ってみた を参考に、CognitoのユーザープールとIDプールを作成。
  2. vue-cliで作成したSPAにシンプルにCognitoログインを組み込む を参考に、ステップ1で作成したIDを使ってログイン画面実装。

※ただし、自分の目的に合わせて微調整は必要。今回の投稿が微調整の参考になれば幸いです。

思った事

  • もし、複数のユーザー毎に異なるS3領域やデータを保存する場合、S3のフォルダ構成やDynamoDBのキー構造も考慮する必要が出てくる。今回のケースは複数ユーザーは考慮しないが、今後そのようなものを設計する場合は、Cognitoでの制御方法を考慮に入れる事が必要そう。
  • Vueに基本機能として認証制御が存在していた。考えれば当然だが素晴らしい。
  • Cognitoとそれ用のjavaScriptライブラリで簡単に認証機能つけられた。素晴らしい。
  • 今回の件だけなら、Cognitoユーザープールだけで十分だったかも。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away