はじめに

ほとんどのWebサービスにおいて、ユーザの認証機能は必要です。 ここでいう認証機能とは以下の機能群を指します。

  • サインアップ
  • サインイン・サインアウト
  • パスワード変更
  • パスワード初期化

昔のWebサービスは、これら認証機能を各サービスごとに独自に実装していました。
しかしパスワードのような秘匿情報を各サービスごとに保持することはセキュリティリスクを高めます。また認証機能を各サービスごと開発することは開発コストの増加につながります。さらにユーザーはサービスごとに使用しているアカウントを把握する必要がありますので利便性が低下します。
その結果、認証機能を外部システムに移譲したい、すでにある認証基盤を流用したい、などの要求がありました。

これらの要求に応えるように、OAuth 2.0やOpenID Connectなどのフェデレーション認証方式の確立やシングルサインオン等、認証機能に関わる実装の選択肢が最近では増えました。

Amazon Cognito User Pool (User Pool) は、ユーザー認証・管理機能を提供するAWSのサービスです。
User Poolを用いると認証機能を簡単に実装できます。こうした一般的なユーザー認証の実装例は、すでに数多くの記事で紹介されています。
しかしWebサービスによっては、一般的なユーザー認証のケースとは異なる、システム運営者がアカウントを管理したいケースもあります。具体的には次のようなケースです。

  • ユーザーのアカウント作成を管理者による承認制にしたい
  • ユーザーのアカウントを管理者が停止できるようにしたい

User Poolはシステム管理者向けの機能も提供しています。このようなケースの実装例を紹介する記事はあまり見当たりません。今回、上記のような要件を開発するにあたり、User Poolのシステム管理者向け機能を用いて対応しました。

本ドキュメントは、User Poolの作成・設定やユーザー管理の具体的な方法を抜粋して説明したものです。本ドキュメントが同様の開発への参考となることを目的としています。

なお、記載しているWebサービスは開発中のものです。

ドキュメントの説明範囲

本ドキュメントの説明範囲は、CognitoのUser Poolのリソース作成や設定、APIを呼び出せる権限設定やAPIを利用するサンプルコードです。
Cognito User Poolのリソース作成や権限設定にAWSマネジメントコンソールを通じて行います。
ユーザー管理・運用APIを利用するサンプルコードはJavaで記述しています。本事例においては、ユーザー管理・運用APIは、AWS ECSにデプロイしたサーバー上で呼び出されます。サーバーはSpring Bootで実装しました。
ユーザー認証のサンプルコードはJavaScriptで記述しています。

※本ドキュメントではCognito User Poolのソーシャルサインイン機能は利用しておりません。

概要

      • Cognito User Poolと管理系APIを利用できる環境を整えた
        • Cognito User Poolリソースの作成
        • 管理系APIを呼び出せる権限の設定
      • APIを利用してユーザーの管理・運用を行った

背景

今回紹介するWebサービスでは、アカウント認証にCognito User Poolを用いることが決定していました。Webサービスのアカウント登録のフローが下記のように決まっていました。

  1. Webサービスのユーザーは最初に利用申請を行う
  2. 管理者が利用申請を審査し承認または却下する
  3. 承認されたユーザーはCognitoユーザーとWebサービスアカウントが作られる
  4. ユーザーに利用承認または却下メールを送信する
  5. 承認されたユーザーは承認メール中のログイン情報を用いてWebサービスを利用する

また、運用についても要件が決まっていました。

  • 管理者はユーザーのアカウントを無効化できる
  • ユーザーは自分でパスワードを変更できる

要件を精査し、AWS構成・シーケンスを決定していきました。

AWS構成・シーケンス

Webサービスでユーザー管理・運用に関わる部分を抜粋したAWS構成を以下に提示します。

ユーザー向けサービスは、ユーザーへWebサービスを提供するAWSリソースをまとめたものを示します。 管理者向けサービスは、管理者へWebサービスを提供するAWSリソースをまとめたものを示します。

ここから各手順を示します。
まず、ユーザーのサービス利用申請時は以下の手順を踏みます。ここでは説明の簡略化のため、ユーザーの利用申請が管理者に承認される時の手順を書いています。

ユーザーはまずユーザー向けサービスにアクセスしサービス利用申請を行います(1)。
ユーザー向けサービスはRDSに利用申請情報を保存します(2)。

管理者は、管理者向けサービスを通じて利用申請情報を確認します(3, 4)。そして利用申請を承認します(5)。 管理者向けサービスは、Webサービスのアカウント情報をRDSに作成します(6)。 次にCognitoにユーザー作成APIを呼び出します(7)。 ユーザー作成直後にユーザーの初期パスワードを設定します(8)。

ユーザー作成と初期パスワード設定が完了すると、Amazon Simple Email Service(SES)を通じて申請結果のメールを送信します。メールにはユーザー認証情報が記載されます(9, 10)。

次にユーザー認証、またはパスワード変更時の手順を説明します。

ユーザーはユーザー向けサービスにアクセスし、メールに記載されたログイン情報をもとにユーザー認証を要求します。同じように、パスワード変更時にはパスワード変更要求を行います(1)。 ユーザー向けサービスは、要求に応じたCognito APIを呼び出し、その結果を受け取ります(2)。

管理者がユーザーのアカウント停止を行う際には、以下の手順を踏みます。

管理者は、管理者向けサービスにユーザーのアカウント停止を要求します(1)。

管理者向けサービスはCognitoのアカウント停止APIを呼び、その呼び出し結果を受け取ります(2)。

シーケンスの決定と共に要件の詳細な仕様が決定していきました。

ユーザー作成・認証・運用の詳細な仕様

詳細な仕様は以下のようになりました。

  • ユーザーはCognitoのサインアップを行わず管理者がCognitoユーザー作成を行う
  • Cognitoユーザーを作成する時とユーザーがWebサービスへ認証する時に必要な情報はメールアドレスとする
  • 初回パスワードは管理者が設定する
    • これは一時パスワードではない
  • ユーザーはWebサービスのアカウントが作成されるとサインイン・サインアウトとパスワードの変更を行える
  • 管理者はユーザーのアカウントを停止できる

特に初回パスワードの設定には注意が必要でした。 User Poolにおいて管理者がユーザーを作成した時のデフォルト動作では、ユーザーの初回パスワードを自動設定し、これが記載されたメールをユーザーへ送信します。これが管理者側でパスワードを設定する仕様と合わないため、Cognitoデフォルトの動作を変える必要がありました。

各AWSリソースの作成手順やサンプルコードの説明

本節よりCognito User Poolリソースの作成手順やサンプルコードを説明していきます。 具体的には下記の手順・サンプルコードとなります。

  • Cognito User Poolとアプリクライアントを作成
  • Cognitoのユーザー管理・運用系APIを呼び出せるようIAMポリシーとIAMロールを作成
  • AWS SDK Javaを用いてCognito APIを呼ぶJavaサンプルコード
  • ユーザー認証系APIを呼ぶJavaScriptサンプルコード

Cognito リソースの作成

ここではCognitoリソースの作成方法を説明します。

Cognitoのユーザープールを作成するAWSマネジメントコンソール画面を開きます。

“プール名”を入力し、User Poolの作成手順を指定する画面です。
今回は”ステップに従って設定する”でユーザープールを作成していきます。

ここから冗長な部分が多くなりますので、重要なステップのみを説明します。

ユーザーにどのようにサインインをするかを指定し、ユーザーが持つ属性を指定するステップです。
ユーザーはメールアドレスとパスワードを使ってサインインすることになっています。ですのでEメールアドレスを「ユーザー名」としてサインアップすることを許可しています。ユーザーが持つ属性もemailにチェックを入れました。

パスワードポリシーの設定と、サインアップに関する設定のステップです。
管理者がユーザーに初回パスワードを設定する時や、ユーザーがパスワードを変更する時には、 このパスワードポリシーに沿う必要があります。
次に”管理者のみにユーザーの作成を許可する”側にチェックしました。この設定により、User Poolはユーザーのサインアップを許可せず、管理者がユーザーを作成することになります。

ユーザープールへのアクセス権限を持つアプリクライアントを設定する画面のステップです。 “アプリクライアントの追加”をクリックします。

アプリクライアントの作成モーダルが表示されたところです。
アプリクライアント名を設定します。
認証フローチェックボックスは図のように設定しました。
“アプリクライアントの作成”をクリックします。

あとは手順に沿って最後まで設定を行っていくと、User Poolとアプリクライアントが作成されます。

管理者がユーザーの作成を行う

管理者はユーザーの作成とユーザーのパスワードの設定を行います。
User Poolでは、管理者がユーザーの作成を行うAPIAdminCreateUserを、管理者がパスワードの設定を行うAPIAdminSetUserPasswordを提供しています。 また、管理者がユーザーのアカウントを停止するAPI AdminDisableUserも提供されています。

管理者向けサービスはSpring Bootで実装されECS上にデプロイされたものです。これがCognito APIを呼び出せるよう権限を設定します。

管理者向けサービスにCognito APIを呼び出せる権限を設定する

AWSマネジメントコンソールから権限を設定する方法を説明します。

IAMポリシーの編集を行うAWSマネジメントコンソール画面を開いたところです。

“サービスの選択”で”Cognito User”と入力したところです。
目的のCognito User Poolsサービスが見つかりました。

Cognito User Poolsサービスを選択後に、’アクション’→’書き込み’の中の以下のAPIにチェックを入れたところです。

  • AdminCreateUser
  • AdminDisableUser
  • AdminSetUserPassword

Cognito User Poolsサービスに対して許可するアクションの設定が完了したところです。
次にリソースを指定します。”ARNの追加”をクリックします。

ARNの入力モーダルが表示されます。
Accountは利用中のAWSアカウントIDが自動的に入力されます。
Regionに適切なリージョンを、User pool Idには前節で作ったUser Pool Idを指定します。

あとは表示される手順に沿ってIAMポリシーを作成します。

次にIAMロールを作成します。

IAMロールの作成を行うAWSマネジメントコンソール画面を開きます。

“または、サービスを選択してユースケースを表示します”内の”Elastic Container Service”を選択します。
選択後、画面下部に”ユースケースの選択”が出現しますので、”Elastic Container Serive Task”を選択します。
“次のステップ: アクセス権限”ボタンが有効になりますので、クリックします。

ロールに割り当てるポリシーを設定します。
先ほど作成したポリシーの名前を入力フォームに入れると候補リストにポリシーが出現します。チェックボックスにチェックを入れます。
あとは表示される手順に沿っていき、IAMロールを作成します。

後続の作業でロールARNの情報が必要になります。
作成されたロールを選択するとロールARNが表示されます。このARNをメモしておいてください。

ECSタスク定義にタスクロールARNを設定する。

作成したIAM Task RoleのARNをECSタスク定義の”taskRoleArn”に設定します。 これで管理者向けサービスにCognito APIを呼び出せる権限を設定出来ます。

管理者向けサービスでユーザー作成、パスワード設定APIを呼ぶ

管理者向けサービスからユーザー管理を行うサンプルコードを提示します。以下のサンプルコードにおいて、Cognito APIを用いてユーザー作成とパスワード設定を行うクラスをCognitoServiceとしました。
上記のサービス利用申請時シーケンスのステップ7、ステップ8に該当します。

@Configuration
public class CognitoCofiguration {
    private final String region = "REGION";

    @Bean
    public AWSCognitoIdentityProvider cognitoClient(
            final AWSCredentialsProvider credentialsProvider /* A */) {
        return AWSCognitoIdentityProviderClientBuilder.standard()
                .withCredentials(credentialsProvider)
                .withRegion(region)
                .build();
    }
}

@Service
public class CognitoService {

    private final AWSCognitoIdentityProvider cognitoClient;

    private final String userPoolId = "USERPOOLID";

    public CognitoService(final AWSCognitoIdentityProvider cognitoClient) {
        this.cognitoClient = cognitoClient;
    }

    public AdminCreateUserResult createUser(final String email) {
        return cognitoClient.adminCreateUser(
                new AdminCreateUserRequest()
                        .withUserPoolId(userPoolId)
                        .withUsername(email)
                        .withMessageAction(MessageActionType.SUPPRESS)); /* B */
    }

    public void setUserPassword(final String username, final String password) {
        cognitoClient.adminSetUserPassword(
                new AdminSetUserPasswordRequest()
                        .withUserPoolId(userPoolId)
                        .withUsername(username)
                        .withPassword(password)
                        .withPermanent(true)); /* C */
    }
}

サンプルコード中のregionにはCognito User Poolリソースを作成したリージョンを、userPoolIdには、そのPoolIdを指定します。 (実際のソースコードでは、region・userPoolIdは環境変数から取得しています。)

A部分のAWSCredentialsProviderクラスは、IAM権限をECS設定から取得します(ECSドキュメントAWS SDK for Javaのcredentials)。
このクラスを利用して、AWSCognitoIdentityProviderインスタンスを作成します。

AWSCognitoIdentityProviderのadminCreateUserメソッドでユーザーを作成します。Cognitoのデフォルトの動作では、ユーザー作成時にメールが送信されます。この動作を抑制するため、B部分MessageActionType.SUPPRESSを指定します。

返り値のAdminCreateUserResultクラスが持つメソッドgetUserName()でユーザー名を取得できます。このユーザー名は次のadminSetUserPasswordメソッドで使用します。

AWSCognitoIdentityProviderのadminSetUserPasswordメソッドでユーザーのパスワードを設定します。設定するパスワードは、Cognito User Poolリソースを作成する時に指定したパスワードポリシーを満たす必要があります。
このAPIのデフォルトではパスワードが一時的なものになります。その状態でユーザーがCognitoにログインすると、Cognitoからユーザーにパスワードを変更するよう要求されます。 パスワード変更を要求されないようにするため、C部分withPermanent(true)でパスワードを恒久的にしています。

このメソッドは他のAPIに比べて新しく(2019/5~)設けられたAPIです。
そのため、このメソッドを利用する際にはAWS SDKのバージョンに注意してください。
今回の開発で利用したspring-cloud-starter-aws:2.2.1.RELEASEに依存するAWS SDK for Java 1.11.415には実装されていませんでした。ですので、開発時点で最新のAWS SDK for Java 1.11.762を使用しました。

ユーザーはサインイン、サインアウト、パスワード変更をCognitoAPIで行う

ユーザーはユーザー向けサービスに対してユーザー認証要求やパスワード変更要求を出します。 ユーザー向けサービスのフロントエンドからCognito APIを呼び出すサンプルコードを以下に提示します。
上記のユーザー認証/パスワード変更時シーケンスのステップ2に該当します。

import {
  AuthenticationDetails,
  CognitoUser,
  CognitoUserPool,
} from 'amazon-cognito-identity-js';

const UserPool = new CognitoUserPool({
  UserPoolId: COGNITO_USER_POOL_ID,
  ClientId: COGNITO_CLIENT_ID,
}); /* A */

const AuthAPI = {
  signIn: (id, password) => {
    return new Promise((resolve, reject) => {
      const authnDetails = new AuthenticationDetails({
        Username: id,
        Password: password,
      });
      const cognitoUser = new CognitoUser({
        Username: id,
        Pool: UserPool,
      }); 

      cognitoUser.authenticateUser(authnDetails, {
        onSuccess: result => {
          resolve(result);
        },
        onFailure: () => {
          reject();
        },
      });
    });
  },

  signOut: () => {
    const currentUser = UserPool.getCurrentUser();
    if (currentUser) {
      currentUser.signOut();
    }
  }

  changePassword: (oldPassword, newPassword, callback) => {
    const currentUser = UserPool.getCurrentUser();
    currentUser.getSession((err, result) => {
      if (result) {
        currentUser.changePassword(oldPassword, newPassword, callback);
      }
      else {
        callback(err, null);
      }
    });
  },
};

export default AuthAPI;

A部分CognitoUserPoolに、Cognito User Poolリソースを作成した時に取得できるUserPoolId, UserPoolClientIDを設定します。

signIn関数の引数id、passwordにはそれぞれメールアドレスとパスワードを設定します。パスワードは管理者が決めた初回パスワードか、後述するユーザーが設定したパスワードです。

changePassword関数は、古いパスワードから新しいパスワードへ変更する関数です。パスワードはCognito User Poolリソースを作成する時に設定したパスワードポリシーを満たす必要があります。

管理者がユーザーを無効化する

上述のクラスCognitoServiceにメソッドを追加する形で、管理者がユーザーを無効化するサンプルを提示します。
上記のアカウント停止時シーケンスのステップ2に該当します。

   public void disableUser(final String username) {
        cognitoClient.adminDisableUser(
            new AdminDisableUserRequest()
                .withUserPoolId(userPoolId)
                .withUsername(username)
        );
    }

このメソッドでユーザーを無効化できます。 usernameには、AdminCreateUserResult::getUserName()で取得する値を指定します。

以上がサンプルになります。

まとめ

Cognito User Poolを用いて管理者がユーザを管理するケースのシステム構成と実現手順の一例を紹介しました。

今回のCognito User Poolの管理系APIを呼び出す環境はECS上のサーバーで、そこではAWS Java SDKを用いました。
この構成は一例で、他に様々な環境や利用SDKを選択できます。環境では、例えばEC2やオンプレミス環境のサーバーを選択できます。またSDKはAWS SDK一覧から選べます。環境が異なるとAPIを呼び出せる権限の付け方が本事例とは異なってきますが、Cognito User Pool APIの利用方法はどの選択でも大体同じになります。

本ドキュメントで示したような設定や実装が、今後のプロジェクトの参考になると考えます。

・Amazon Web Services、『Powered by Amazon Web Services』ロゴ、[およびかかる資料で使 用されるその他の AWS 商標] は、米国および/またはその他の諸国における、Amazon.com, Inc. または その関連会社の商標です。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。