0. 概要
Cognito ID Poolの機能を使うと、何かのサービスで認証に成功したユーザに対してAWS認証情報を発行できます。このAWS認証情報と、AWS SDK for JavaScriptがあれば、ブラウザ側のコードだけでAWSのサービス触り放題じゃね・・・?と思ったので、やり方を調べてみました。
認証サービスは何でもいいのですが、ユーザごとに権限を分ける方法を調べたかったので、Cognito User Poolを使います。
イメージはこんな感じです。
はじめはamazon-cognito-identity-jsを使ってあれこれしようと思ったのですが、User PoolとID Poolの連携がどうにもうまく行かないしそもそも資料もあんまりない。あれこれ調べているうちに、いつのまにか新しいライブラリが登場していることを知りました。
それがAmplify Framework
です。
https://github.com/amazon-archives/amazon-cognito-identity-js
既存のライブラリを組み合わせたフレームワークなので、これならUser PoolとID Poolの連携がうまく行きそうです。さっそく使ってみます。
1. 準備編
フレームワークを触る前に、最初に示した概要図まわりの状態を作ります。
必要になるのは、「ユーザ」「グループ」「ロール」「ポリシー」です。
この辺りの詳しい構成は、概要図では省略してしまったのでもう少し詳細な図を載せます。
-
User Pool
では「ユーザ」と「グループ」を管理します。ユーザはグループに所属します。 -
IAM
では「ポリシー」と「ロール」を管理します。ポリシーは「何が出来るか」の定義で、ロールはポリシーをまとめたものです。 - この2つをつなぐのが
ID Pool
です。User Pool
によって認証されたユーザのグループを確認し、それに対応したロールを持つAWS認証情報を返します。
以上を踏まえて、さっそく環境を作っていきましょう。
※ この図の構成が分かっている方は準備編は読み飛ばして構いません。
1.1 User Pool
ユーザプールと、それを利用するアプリクライアントを作成します。
右側の「ステップに沿って実行する」から作成します。
以下の2点の修正箇所以外は全てデフォルトのまま「次のステップへ」ボタンで進んで構いません。
修正箇所1. 必須属性の解除
ユーザIDとパスワードのみで検証するシンプルなアプリを作りたいので、メールアドレスの必須チェックは外します。
修正箇所2. アプリクライアントの作成
プールIDとプールARNの確認
最後の確認画面で「プールの作成」ボタンをクリックしたら、ユーザプールの作成は完了です。「プールID」と「アプリクライアントID」は後ほど使うのでメモしておきます。
1.2 IDプールの作成
ユーザプールと連携するIDプールを作成します。
↑「認証に成功しているユーザ」「していないユーザ」それぞれに与えるロールを作成しても良いか聞いています。今回は、Cognito User Poolのグループに対応したロールを与える予定なので、使用しません。
↑最後に表示されるIDプールのIDをメモしておきます。
グループに対応したロールのAWS認証情報を提供してもらえるように、設定を変更します。
1.3 ポリシーの作成
一度Cognitoを離れてIAMに移動し、ポリシーを作成します。
ポリシーでは、ログインユーザが操作できる権限の範囲を作成します。
検証環境であればなんでもいいのですが、公開環境で適当に大きな権限を与えてしまうと当然、多大な被害が出てしまうので慎重に。
今回は、Cognitoに登録されているユーザのリストを表示できる権限を作ってみます。
1.4 ロールの作成
これで管理者用のロールが作成できました。
説明は省きますが、もう一つ、一般ユーザ用の何もポリシーを持たない「amplify-test-user-role」を作成しておきます。
1.5 ログイン用ユーザの作成
1.6 グループの作成
さらにもうひとつ、管理者ではないユーザ用のグループ「user」を作ります。IAMロールには、何もポリシーを持たない「amplify-test-user-role」を設定しておきましょう。
このグループには、no_roleユーザを所属させておきます。
これで環境設定は終わりです。
2. クライアント側コードの作成
ここからがやっと本題です。Amplify Frameworkを使って簡単な認証機能と、AWS認証情報を使った利用者情報の取得を試してみましょう。
2.1 プロジェクト作成
create-react-app
コマンドでさくっと作ります。
Amplify Framework
にReact
は必須ではないので何でもいいです。
# プロジェクトの作成
create-react-app amplify-test
cd amplify-test
# aws-amplifyとaws-sdkをインストール
npm install aws-amplify aws-sdk --save
2.2 Amplify初期化コードの埋め込み
最初に読み込まれる場所に、Amplifyの初期化コードを書きます。
準備編で作ったIDプール、ユーザプール、アプリクライアントのIDを、Auth
プロパティの中に指定します。
import Amplify from "aws-amplify";
// Amplifyの設定
Amplify.configure({
Auth: {
// IDプールのID
identityPoolId: "ap-northeast-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
// リージョン
region: "ap-northeast-1",
// ユーザプールのID
userPoolId: "ap-northeast-1_XXXXXXXXX",
// アプリクライアントID
userPoolWebClientId: "XXXXXXXXXXXXXXXXXXXXXXXXXX",
},
});
2.3 ログイン処理
Cognitoにログインする仕組みを作ります。
AmplifyのAuthクラスに、Cognitoにログインするための関数signIn
が用意されています。
その他の関数の仕様はこちらを参照してください。
https://aws-amplify.github.io/amplify-js/api/classes/authclass.html
使い方のサンプルもこちらにたくさん載っています。
https://aws-amplify.github.io/docs/js/authentication
import { Auth } from "aws-amplify";
/**
* ログイン処理
* @param {string} userid
* @param {string} password
*/
export async function login(userid, password) {
const user = await Amplify.Auth.signIn(userid, password);
if (user.challengeName === "NEW_PASSWORD_REQUIRED") {
// 初回ログイン時は、必ずパスワードを再設定するように求めてくる
// 新しいパスワードは同じでも構わないので、completeNewPasswordに同じパスワードを渡してしまう
await Auth.completeNewPassword(user, password);
}
}
2.4 利用者情報取得処理
こちらは管理者権限の利用者だけが使える処理です。
currentCredentials
から、現在ログイン中の利用者が所属するグループに対応したAWS認証情報が取得できます。
これをCognitoUserPoolsの管理クラスに渡すことで、管理機能が使えるようになります。
/**
* Cognito User Pool内のユーザを取得する
* @return ユーザ一覧
*/
export async function getCognitoUsers() {
// ログイン中のユーザのグループに対応したAWS認証情報を取得する
const credentials = await Auth.currentCredentials();
// Cognito User Poolの操作クラスにAWS認証情報を渡して初期化する
const cognitoServiceProvider = new AWS.CognitoIdentityServiceProvider({
credentials: Amplify.Auth.essentialCredentials(credentials),
region: "ap-northeast-1",
});
// ユーザプール内のユーザを取得する (AWS認証情報がないと使えない部分)
const result = await cognitoServiceProvider.listUsers({ UserPoolId: "ap-northeast-1_XXXXXXXX" }).promise();
const users = result.Users;
return users;
}
2.5 ログインフォーム、利用者一覧表示画面の作成
Amplify Frameworkとは関係ない部分ですが、一応載せておきます。
import React, { useState } from "react";
import { getCognitoUsers, login } from "./cognito";
function App() {
// ユーザID
const [userId, setUserId] = useState("");
// パスワード
const [password, setPassword] = useState("");
// ログイン後に、Cognitoから取得する予定のユーザ一覧
const [users, setUsers] = useState([]);
// エラーメッセージ
const [error, setError] = useState("");
// ユーザID変更時
const onChangeUserId = (e) => {
setUserId(e.currentTarget.value);
};
// パスワード変更時
const onChangePassword = (e) => {
setPassword(e.currentTarget.value);
};
// ログイン時
const onLogin = async (e) => {
e.preventDefault();
setError(""); // エラーメッセージクリア
try {
// ログインを実行する
await login(userId, password);
} catch (e) {
setError(e.message); // エラーメッセージ表示
}
};
// 利用者一覧取得時
const onClickGetUsers = async () => {
setError(""); // エラーメッセージクリア
try {
// ログインに成功したら、ユーザ一覧を取得する
const users = await getCognitoUsers();
setUsers(users);
} catch (e) {
setError(e.message); // エラーメッセージ表示
}
};
return (
<div className="App">
<form onSubmit={onLogin}>
<div>
USER:<input type="text" onChange={onChangeUserId} value={userId} />
</div>
<div>
PASS:<input type="password" onChange={onChangePassword} value={password} />{" "}
</div>
<button type="submit">ログイン</button>
</form>
<button type="button" onClick={onClickGetUsers}>
利用者一覧取得
</button>
{users.length > 0 ? (
<table>
<thead>
<tr><th>利用者名</th><th>状態</th><th>作成日</th></tr>
</thead>
<tbody>
{/* ユーザの数だけループ */}
{users.map((user) => {
return (
<tr key={user.Username}>
<td>{user.Username}</td><td>{user.UserStatus}</td> <td>{user.UserCreateDate.toString()}</td>
</tr>
);
})}
</tbody>
</table>
) : null}
<p className="error-message">{error}</p>
</div>
);
}
export default App;
3. 動作確認
3.1 ログインなしの場合
まずは、ログインをせずにCognitoの利用者一覧を取得してみましょう。
「AWS認証情報がない」というエラーが出ています。
3.2 ログイン失敗の場合
ログインできていないので、当然「AWS認証情報がない」というエラーが出ます。
3.3 管理者グループではない利用者の場合
管理者権限を持たない利用者でログインしてみます。
エラーとしてこのようなメッセージが返って来ました。
User: arn:aws:sts::000000000000:assumed-role/amplify-test-user-role/CognitoIdentityCredentials is not authorized to perform: cognito-idp:ListUsers on resource: arn:aws:cognito-idp:ap-northeast-1:000000000000:userpool/ap-northeast-1_XXXXXXXXXX
amplify-test-user-role
というロールがIDプールから発行されましたが、このロールはcognito-idp:ListUsers
を実行する権限(ポリシー)を持っていないので、エラーが発生しているのがわかります。
3.4 管理者グループの利用者の場合
最後に、管理者権限を持つ利用者でログインしてみます。
ユーザー「iewori」はロールamplify-test-admin-role
を持っており、このロールはcognito-idp:ListUsers
を実行する権限(ポリシー)があるので取得に成功しています。
まとめ
Amplify Framework
で調べると一番よく使われるケースとしてはS3
の操作やGraphQL
との連携あり、それらはStrageやAPIとして便利なクラスがすでに提供されています。なので今回のCognito管理機能の利用は実は特殊ケースです(今のところ)。
そんな特殊ケースでもAWS認証情報さえ取得できればサーバー側のコードを書くことなく実装できてしまうことが、お分かりいただけたかと思います。
AWSのサービスを直接触る形になるのでAPIGateway
とLambda
を通じたREST API形式よりもセキュリティ上の設計をより厳密にする必要はありますが、コード量削減と効率アップのためにチャレンジしてみてはいかがでしょうか。