こんにちは。しげぞうです!
みなさんはWebアプリケーションやスマホアプリを開発する際、ユーザー管理や認証はどうしていますか?
個人としては最近、ユーザー認証基盤を提供するAWSサービス Amazon Cognito がいい感じだなって思っています!
先日の業務打ち合わせでも「Cognito 使ってみたいよね~」という話も出てきて、試したい欲が急上昇!!
手始めにCognitoを使ったユーザーのサインアップ機能を実装してみました!
そもそもCognitoとは
Cognitoは、Webアプリケーション/モバイルアプリケーションで利用可能なユーザー認証機能を提供するAWSのフルマネージドサービスです。単にユーザーのサインイン/サインアップ機能を提供するだけでなく、ユーザーに対して他AWSサービスへのアクセスコントロールを提供することができます。特に後者の機能が便利で、AWS SDKを利用して開発するときに直接コードにIAMの認証情報(AccessKeyId,SecretAccessKey)を埋め込む必要がなくなるので、セキュアなAPIアクセスが実現できます!
Cognitoを利用する上で理解しておくべき概念として、ユーザープール と IDプール があります。これらについて簡単にさらっておきます。
- Cognitoユーザーを管理するユーザーディレクトリとしての役割を担う
- ユーザーのサインアップ/サインインはユーザープールのみで実現可能で、外部IDプロバイダとの連携にも対応
- 管理者によるユーザーの作成とユーザー自身によるサインアップの両方に対応
- Cognitoで管理するユーザーは基本情報(ユーザー名, 誕生日, 性別 etc...)を属性として保持できる
- 属性はユーザープールごとにカスタムで追加もできるが、更新頻度の高いデータを持たせるのはアンチパターン
- Cognitoで管理するユーザーに一時的なAWS認証情報(Temporary AWS Credentials)を提供する役割を担う
- 認証されたユーザーだけでなく非認証ユーザー(いわゆるゲストユーザー)もサポート
- Temporary AWS Credentialsとして
AccessKeyId,SecretAccessKey,SessionTokenの3つを提供 - AWSサービスへのアクセスコントロールは独自のIAMロールで管理
- ゲストユーザーに対してもIAMロールを設定できる(←これ面白い)
さっそく試してみる!
まずCognitoを利用する環境整備から
Cognitoを利用してユーザー管理やAWSサービスへのアクセスコントロールを実現するには、まず前章で出てきたユーザープールやIDプールなどの環境が必要になります。
これらはCognitoの管理コンソールをポチポチして用意できるのですが、せっかくなので、CloudFormation テンプレートを使って環境を整備できるようにしました。(コードで管理することで再利用性も高く、変更を差分管理できるのがGood!)
ここでテンプレートをぜんぶ載せすると膨大な量になってしまうので、GitHub に挙げておきます。ここでは、今回のメインになるユーザープール(UserPool)とユーザープールクライアント(UserPoolClient)の設定項目について説明します。
まず、ユーザープール(UserPool)
{
"CognitoUserPool": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"UserPoolName": "nuxt-cognito-user-pool",
"Policies": {
# パスワード認証で利用するパスワードポリシーを設定します
# 今回の場合、大文字/小文字/数値をそれぞれ含んだ8桁以上の文字列
"PasswordPolicy": {
"MinimumLength": 8,
"RequireUppercase": true,
"RequireLowercase": true,
"RequireNumbers": true,
"RequireSymbols": false,
"TemporaryPasswordValidityDays": 7
}
},
# ユーザープールに設定する属性を設定します
# 今回は、ニックネーム(nickname)を必須項目に設定
"Schema": [
{
"Name": "nickname",
"AttributeDataType": "String",
"DeveloperOnlyAttribute": false,
"Mutable": true,
"Required": true,
"StringAttributeConstraints": {
"MinLength": "0",
"MaxLength": "2048"
}
}
],
# ユーザーの検証を何で行うか(メールor電話番号)
"AutoVerifiedAttributes": [
"email"
],
# サインアップ時に何でユーザーを特定するか(メールor電話番号)
"UsernameAttributes": [
"email"
],
# Cognitoのメールに関する設定
"EmailConfiguration": {
# 送信元メールアカウントをどれにするか(Amazon SESも設定可能)
# 今回は、from: no-reply@verificationemail.com となるように設定
"EmailSendingAccount": "COGNITO_DEFAULT"
},
・・・
# 検証用メッセージについての設定
"VerificationMessageTemplate": {
"DefaultEmailOption": "CONFIRM_WITH_CODE"
}
}
}
}
次に、ユーザープールクライアント(UserPoolClient)
{
"CognitoUserPoolClient": {
"Type": "AWS::Cognito::UserPoolClient",
"Properties": {
"ClientName": "nuxt-cognito-user-pool-client",
# 利用アプリに対して認証を行うか
"GenerateSecret": false,
# トークンの有効期限(日)
"RefreshTokenValidity": 7,
# 認証フローに関する設定
"ExplicitAuthFlows": [
"ALLOW_REFRESH_TOKEN_AUTH",
# 認証で利用するJavaScript SDKがSRPプロトコルベースを採用しているので今回指定
"ALLOW_USER_SRP_AUTH"
],
# ユーザー存在エラーの設定
# SighUpの時、既に存在する username を登録しようとするとエラーとして扱う
"PreventUserExistenceErrors": "ENABLED",
"UserPoolId": {
"Ref": "CognitoUserPool"
}
}
}
}
Cognitoを使ってユーザーのサインアップを試してみる
Cognitoを使ったユーザー認証を試してみるのに、今回はWebアプリアーキテクチャの主流になりつつあるJamstackも意識して、実務でも利用したことのある Nuxt のフレームワークでやってみることにしました。
サインアップですが、以下の流れで実現します。

シンプルなサインアップ画面です。ユーザープール作成時、属性にnicknameを必須に設定したのでそちらの入力欄も用意しました。ちなみに画面の装飾には、Tailwind CSS というCSSフレームワークを利用しています。(TailwindUI というコンポーネント集がとても便利なので、今回はそちらを利用させてもらいました)
Cognitoでのサインアップ処理ですが、 amazon-cognito-identity-js というnpmライブラリを利用して実装します。
import { Component, Vue } from "nuxt-property-decorator";
import {
CognitoUserPool,
CognitoUserAttribute,
} from "amazon-cognito-identity-js";
@Component({ auth: false })
export default class Signup extends Vue {
email = "";
password = "";
confirmPassword = "";
nickName = "";
async signup(): Promise<void> {
// 入力チェック
if (this.password != this.confirmPassword) {
this.errorMsg = "パスワードと確認用パスワードが一致しません。";
return;
}
// ユーザープールクライアントを初期化
const userPool = new CognitoUserPool({
ClientId: process.env.COGNITO_CLIENT_ID || "",
UserPoolId: process.env.COGNITO_USER_POOL_ID || "",
});
// ニックネームを登録用パラメータに整形
const attributeList: CognitoUserAttribute[] = [];
const attributeUserNickName = new CognitoUserAttribute({
Name: "nickname",
Value: this.nickName,
});
attributeList.push(attributeUserNickName);
// 入力したユーザー情報をもとに登録処理を実施
const self = this;
await userPool.signUp(
this.email,
this.password,
attributeList,
[],
function (err) {
// エラー時
if (err) return;
// アクティベーション画面に遷移
self.$router.push("/activate");
}
);
}
}
①仮ユーザー登録 ですが、ユーザープール構築時に生成された認証キーを使ってクライアントの初期化(new CognitoUserPool)を行った後、登録用パラメータを渡して登録処理(userPool.signUp)を呼び出せばOKです。
登録処理にはコールバック関数が用意されているので、処理結果に応じて以降の処理を独自にハンドリングできます。今回の場合、登録に成功すると入力したメールアドレス宛にユーザー登録用の確認コードが送信されるので、その確認コードを入力する専用画面に遷移させています。
②本登録 のロジックですが、基本の流れはユーザー登録処理と同じですのでここでは省略します。全体のソースコードをGitHubにアップしてますので、詳細はそちらを覗いてみてください。
ちなみにクライアントIDやユーザープールIDなどのCognito認証キーは、dotenvライブラリを利用して環境変数から参照しています。
# プロジェクト直下に.envファイルを作成して以下のように設定
COGNITO_USER_POOL_ID = 'ap-northeast-1_xxxxxxxx'
COGNITO_CLIENT_ID = 'xxxxxxx...'
実際にサインアップを試してみます。
まず仮登録画面で必要なユーザー情報を入力して仮登録を行います。

仮登録完了後、本登録画面でメールアドレスと確認コード(仮登録時に入力したメールアドレスに配信される)を入力しユーザーを検証します。

本登録成功!(サンプルでは、本登録完了が分かるようwindow.alertを出すようにしています)

本登録も完了したので、実際にユーザーが登録されているかCLIコマンドで確認してみます。
aws cognito-idp list-users --user-pool-id ap-northeast-1_xxxxxxxx --profile xxxxxxxxx

用意したユーザープールにユーザーが追加されていることが確認できました!
最後に
今回Cognitoを試してみましたが、圧倒的に早く、しかも簡単にユーザー認証の仕組みを整えれるすげえサービスだと実感しました。。Cognitoを利用すればサーバ側のフレームワークでユーザー認証を作りこむ必要がなくなるし、外部IDフェデレーションも標準で利用できるし、SDKも充実しているので、今後ユーザー認証に使わない選択肢はないなと思いました。
また、Webアプリの基盤を簡単に作れる Amplify では、このCognitoを内包しているので、より簡単にユーザー認証の仕組みをアプリに組みこめるそうです。Amplifyも試してみたい。。
今回はCognitoを利用したサインアップを試してみましたが、この延長で今後は以下のこともやってみようと思っています!
- ユーザーによるサインイン処理
- 認証ユーザーによる他AWSサービスの呼び出し
以上、最後まで読んでいただきありがとうございました!
