Cognito とは?
Amazon Cognito は、各種アプリケーションにユーザーの概念を取り入れて、サインアップやサインインなどを行えるようにするサービスです(OpenID Connect 準拠の Identity Provider (IdP) のひとつです)。 Cognito は大きく下記の 2 つの機能から成ります。
- ユーザープール: ユーザーを管理する。任意のアプリに、ユーザー登録(サインアップ)や、認証(サインイン)の機能を付けることができる。
- ID プール: 上記の仕組みでサインインしたユーザーに対して、AWS サービスへのアクセス権限を付加する。
単純にユーザー管理だけをしたいのであれば、前者の「ユーザープール」の機能だけを使うことができます。 実際には、認証後に AWS のリソースにアクセスさせることが多いと思いますので、後者の「ID プール」の仕組みを使って IAM の権限割り当てを行うことになります。
認証の仕組みに関しては、5 万 MAU(月間アクティブユーザー)まで無料で使えるので、個人で作成しているアプリでも使いやすいと思います。 ただし、無料枠を超えると 1 ユーザーあたり 1 円くらいかかり、多要素認証などを使おうとするともう少しお金がかかります。 詳しくは、Cognito の料金)のページを参照してください。
通常、Web サイトに認証機能を付けたいと思うと、ユーザー管理の仕組みや、サインイン画面などの UI を作成する必要がありますが、Cognito はこれらをまとめて提供してくれます。 ここでは、Cognito のユーザープールの作成から、React アプリでの認証までを説明します。
Cognito にユーザープールを作成する
準備として、Cognito のユーザープールを作成しておきます。
- Cognito のマネージメントコンソールにアクセス
ユーザープールの管理
→ユーザープールを作成する
を選択します- ユーザー作成時の条件 (Attribute) などを設定して作成ボタンを押します
プール名は、適当に myapp-test
とでも付けておけば大丈夫です。
本番環境用のユーザープールと、開発/テスト用のユーザープールは分けて作っておくのがよいので、ここではサフィックスとして -test
を付けておきました。
デフォルト設定でサクッと作成してしまうこともできるし、いろいろな条件(メールアドレス必須とかパスワード強度とか)を指定することもできます。
ここでは、とりあえずデフォルト設定で適当に作成してしまいましょう。
ユーザー名でなく、メールアドレスでもログインできるようにしたいときは、属性 (Attribte)
のタブで、ユーザー名 - 検証済みの E メールアドレスでのサインインも許可
にチェックをいれておきます。
デフォルト設定は次のような感じになってます。
- ユーザーは自由にサインアップできる(Eメールアドレスが必須)
- ユーザー名の大文字・小文字は区別しない (Maku = maku = mAkU)
- パスワードは 8 文字以上(大文字 + 小文字 + 特殊文字 + 数字)
ユーザープールを作成すると、Cognito のマネージメントコンソール上から、ユーザーの追加や削除ができるようになります。
次に、「アプリクライアントの登録」という作業を行います。 これは、Web アプリやモバイルアプリなど、独自のアプリから Cognito のユーザープール(および ID プール)を利用するための設定です。
(参考)OAuth 2.0 の認可コードフロー
ここは読み飛ばしても構いませんが、OAuth 2.0 の認可コード・グラントタイプ・フロー (RFC 6749 4.1) を大まかにでも理解していると、Cognito での各種設定の意味がよく分かるようになります。
OAuth では認可サーバーを用いて、Web API の呼び出しに必要なアクセストークンを取得するまでの仕組みを定義しており、Cognito の認証&認可もこの仕組みを利用しています。 正確には、OAuth はアプリにアクセス権限を与える「認可」の仕組みであって、サインインなどの「ユーザー認証」に関しては定義していませんが、ここではわかりやすさのために記載しています。
下記は、OAuth の 認可コードフロー (Authorization code grant) で定義されているアクセストークン取得の流れです。 Cognito の設定項目の中にも、まさに、OAuth の認可コードフロー (Authorization code grant) を有効にする、というチェックボックスがあります、
- アプリが認可サーバー(認可エンドポイント)に認可リクエスト (with クライアント ID)を投げると、リソース参照の同意を求める画面が表示される。通常は、そのリソースを提供するサービスへのサインイン(認証)を伴うことになる(他のユーザーのリソースを勝手にアクセス許可できないので当たり前と言えば当たり前)。
- ユーザーがサインインしてリソースアクセスを了承すると、認可サーバー(認可エンドポイント)から 認可コード (Authorization code) が返される。このとき、あらかじめクライアント ID に紐づけられている URL にリダイレクトされて、認可コード付きアドレスで Web アプリ側の画面に戻ってくる。
- Web アプリは認可サーバー(トークンエンドポイント)に認可コードを送って アクセストークン (Access token) を受け取る。
アクセストークンを取得したアプリは Web API を呼び出して、リソースサーバーから情報を取得できるようになります。 リソースサーバーは送られてきたアクセストークンが正しいものか判断した上で、問題なければ情報を返します(例えば、認可サーバーのイントロスペクションエンドポイントにアクセストークンを送ることで有効性を判断します)。 リソースサーバーと認可サーバーは同一であることがよくあります。
アクセストークンというのは、限られたリソースのみ(スコープといいます)にアクセスを許可するためのキーのようなものです。 このアクセストークンの定義と取得の流れが OAuth 認可フレームワークの要です。
ユーザープールにアプリクライアント情報を登録する
クライアント ID の発行
任意のクライアントから Cognite のユーザープールにアクセスするには、あらかじめクライアント情報を登録して、接続用の クライアント ID を発行しておく必要があります。 複数のクライアントタイプ(Web アプリやモバイルアプリ)からのアクセスを想定しているのであれば、それぞれを別のクライアント情報として登録することが推奨されています。 これは、アクセストークン取得のフローなどが異なるからです(参考: OAuth 2.0 - 2.1 クライアントタイプ)。
- Cognito のマネージメントコンソールで対象のユーザープールを選択します
アプリクライアント
→アプリクライアントの追加
を選択します- 適当な「アプリクライアント名」を入力します(
myapp-test-browser
) など クライアントシークレットを作成
のチェックを外します(これはサーバーサイドにキーを埋め込んだりして、AWS の API 呼び出し時に直接指定したいときのもの)アプリクライアントの作成
ボタンを押します
上記のようにアプリクライアントの ID が発行されるので、これをメモしておきます。
サインイン画面用のドメイン名を設定
Cognito はサインアップやサインインのための Web ページを提供してくれるのですが、そのための一意なアドレスを設定しておく必要があります。
- ユーザープールのサイドバーから、
アプリの統合/ドメイン名
を選択します - 適当なドメイン名を設定して
変更の保存
をクリックします
コールバックアドレスの登録
次に、Web ブラウザ上のアプリから認証&認可を行うために、作成しようとしている Web サイトの URL をコールバックアドレスとして登録します。
https://<任意>.auth.ap-northeast-1.amazoncognito.com
など)で行われるため、一時的に Web ブラウザの URL は、自分のサイトから離脱することになります。
サインインが終了したあとに、もとの Web サイトに戻ってくるために、このコールバックアドレスに対してリダイレクトしてもらう必要があります。- ユーザープールのサイドバーから、
アプリの統合/アプリクライアントの設定
を選択します。 - ID プロバイダ (IdP) として、今回作成した
Cognito User Pool
を選択します(Cognito は他にも Google や Facebook のアカウントを使えるよう設定できます)。 - サインイン後のリダイレクト先 URL を指定します(例:
https://myapp.example.com/
)。基本的にhttps://
で始まるアドレスが必要ですが、テスト用クライアントであれば、http://localhost:3000/
のようなローカルアドレスを指定できます。 - OAuth 2.0 の設定では、認証フローとして
Authorization code grant
(前述の認可コードフロー)を選択し、必要なスコープ(アクセストークンで扱える権限)を選択します。スコープは迷ったらとりあえず全部選択しておきます。 - 最後に
変更の保存
を押して設定完了です。
サインイン画面を表示してユーザー登録してみる
上記の設定が完了すると、「アプリクライアントの設定」の下の方の ホストされた UI を起動
というリンクが有効になります。
このリンクをクリックすると、Cognito が提供してくれるサインイン(サインアップ)画面が表示されます。
デフォルトのサインイン画面はこのようにシンプルですが、もちろんカスタマイズできます。
今回はまだユーザーを登録していないと思いますので、ここの Sign up
リンクから、新しいユーザーをユーザープールに登録してみましょう。
登録時に指定したメールに送られてくる確認コード (verification code) を入力すれば、ユーザーの登録が完了するはずです。
確認コード入力後に、先ほど設定したリダイレクト先アドレス(例: http://localhost:3000/?code=3bc09fe0-ba9e-4b88-98c2-58eacdc99006
)に飛ぶので、Web ブラウザではページが見つからないというエラーになりますが、この段階では無視して大丈夫です。
Cognito サービス側のユーザーの登録処理と、Eメールアドレスの検証処理自体は正しく完了しています。
Cognito のコンソールで「ユーザーとグループ」タブを選択すれば、そのユーザーが登録されていることを確認できるはずです。
Node.js アプリからサインインしてみる
いよいよ、独自アプリから Cognito による認証を使用してみます。 React アプリ内からサインイン画面を表示したりする前に、まずは単純な Node.js のコンソールアプリで Cognito 認証を試してみます。 aws-amplify という npm パッケージを使うと簡単です。 AWS Amplify サービス自体を使うわけではないのですが、Cognito 関連の SDK もここに含まれているのでこれを使います。
次のコードを実行してユーザー認証に成功すると、標準出力にアクセストークンなどの情報が出力されます。 もちろんユーザー情報は、前述の作業で登録したユーザーのものを指定してください。
Amplify.configure()
で指定する ID 情報は、Cognito マネージメントコンソールの、ユーザープール管理画面で確認できます。
userPoolId
:全般設定
タブのプール ID
userPoolWebClientId
:アプリクライアント
タブのアプリクライアント ID
React アプリでサインインが必要な画面を作る
@aws-amplify/ui-react パッケージは、Cognito を利用するための React コンポーネントを提供します。
aws-amplify
パッケージに加えて、このパッケージをインストールします。
このパッケージが提供する AmplifyAuthenticator コンポーネントを使用すると、Cognito で サインイン済みでないと表示できないページ を作成することができます。
下記は Next.js アプリのカスタム App (pages/_app.tsx
) 実装例ですが、このように全体を AmplifyAuthenticator
で囲むと、ページ全体がサインイン済みのときのみ表示されるようになります。
Web ブラウザ上で動作する React アプリにおいても、Cognito ユーザープールにアクセスする前に、Amplify.configure 関数 で接続情報をセットしておかなければいけないのは同様です。
サインインしていない状態では、AmplifyAuthenticator
コンポーネントは、次のようにサインイン画面を表示してくれます。
サインインすると、次のように通常のページが表示されます。
今回は、先頭に AmplifySingOut
コンポーネントを配置しているので、サインアウト用のボタンが表示されています。
上記のコードで何となくサインインが必要な Web サイトができた感じがしますが、実際には @aws-amplify/ui-components
モジュールが提供する AuthState
の状態を見て、サインイン済みかどうかを判断して表示処理を分岐させた方がよいようです(おそらく、AmplifyAuthenticator
コンポーネントのレンダリングが常に走ってしまうのを防ぐため)。
このあたりは、Amplify SDK の公式サイト にも recommended way として記載されています。
例えば、次のような感じで表示部分の場合分けをすればよさそうです。
上記の例では、useAuthState()
というカスタムフックを作成して、現在のサインイン状態やユーザー情報 (CognitoUserInteface
) を簡単に取得できるようにしています。
下記はこのカスタムフックの実装例ですが、もう少し綺麗に書けるうまいやり方があるといいなぁ。。。
副作用フック (useEffect
) の中の非同期 API 呼び出しで、状態セットしているのも若干問題ありですがとりあえずは動作します。
できれば公式 SDK でこういうフックサポートして欲しい。
(おまけ)サインイン状態とユーザー名の取得
次の UserInfo
コンポーネントは、Cognito でサインインが完了して、そのユーザー情報が取得できているときは、Hello, <ユーザー名>
と表示し、それ以外の場合は Not signed in
と表示します。
もっとも、サインイン後にしか描画しないページコンポーネントの中でこのコンポーネントを使うのであれば、Not signed in
の文字を見ることはありません。
(おまけ)Sign-In 画面や Sign-Up 画面をカスタマイズする
Amplify ライブラリが提供するデフォルトの ユーザー作成画面 (Sign Up) には、「電話番号 (Phone Number)」の入力欄などが表示されます。
この画面の表示項目をカスタマイズするには、AmplifyAuthenticator
コンポーネントの下に AmplifySignUp
コンポーネントを配置して、その属性で表示内容を指定します。
次の例では、「サインアップ画面 (AmplifySignUp
)」と「サインイン画面 (AmplifySignIn
)」をカスタマイズしています。
サインイン画面から、「Create account」のリンクを消すには、AmplifySignIn
コンポーネントの hideSignUp
属性を true に設定します(=
以降は省略可)。
参考
関連記事
- DynamoDB を Node.js で操作する(SDK ver.2 の場合)
- AWS CLI でアクセスできるのに AWS SDK で Access Denied (403) になるとき
- AWS CloudFormation の設定例: SNS トピックを Lambda 関数からサブスクライブする
- AWS SNS トピックから通知されるイベントデータの例
- Lambda 実装例: S3 へのアップロードを SNS で通知して Lambda から読み込む
- AWS CloudFormation で SNS トピックのリソースを生成する
- AWS CloudFormation の設定例: S3 通知を SNS トピックに Publish する