AWS Cognitoとは
Amazon Cognitoとは、AWSが提供するサービスの一つで、ウェブアプリケーションやモバイルアプリにユーザーの認証機能の実装することができるサービスです。Cognitoは認証、認可、ユーザー管理をサポートしており、ユーザー名とパスワードを通じてサインインする方法や、Google、Appleなどのサードパーティーを通じたソーシャルログインが可能です。
Cognitoの特長
Amazon CognitoにはユーザープールIDプールの二つの主要コンポーネントがあります。
ユーザープール
ユーザープールとは、IDやパスワードの認証情報をアプリ内部のユーザーディレクトリという領域に保存し、その情報を利用してサインアップやサインインなどの「認証」を行う機能です。Cogitoはフルマネージドサービスであるため、インフラストラクチャを意識せずに実装することができます。ユーザープールは次のの機能を提供しています。
- サインアップおよびサインインサービス
- ユーザーをサインインするためのカスタマイズ可能な組み込みWeb UI
- Facebook、Googleでのソーシャルサインイン、Login with Amazon、Sign in with Apple、ユーザープールからのSAMLおよびOIDC IDプロバイダー経由のサインイン
- ユーザーディレクトリとユーザープロファイルの管理
- 多要素認証 (MFA) などのセキュリティ機能、漏洩した認証情報のチェック、アカウントの乗っ取りからの保護、電話とEメールによる検証
- カスタマイズされたワークフローとAWS Lambdaトリガーによるユーザー移行
IDプール
IDプールとは、Amazon Cognitoのユーザープールに加えて、外部IDプロバイダーと連携しながらAmazon S3やDynamoDBなどのAWSの各サービスに対する「認可」を行う機能です。ユーザーは一時的なAWS認証情報を取得して、他のAWSサービスにアクセスすることができます。IDプールは匿名ゲストユーザーと、IDプールのユーザーを認証するのに使用できる次のIDプロバイダーをサポートしています。
- Amazon Cognito user pools
- Facebook、Googleでのソーシャルサインイン、Login with Amazon、Sign in with Apple
- OpenID Connect (OIDC) プロバイダー
- SAML IDプロバイダー
- デベロッパーが認証したID
なお、ユーザープロファイル情報を保存するには、IDプールをユーザープールに統合する必要があります。
ユーザープールとIDプールの違い
ユーザープールは「認証」を処理しますが、IDプールは「認可」を処理するという違いがあります。認証とはアクセスしてきたユーザーが誰であるかを判定する処理のことです。一方、認可とは認証の後にそのユーザーが利用しようとしているサービスが利用許可されているかを判定する処理のことです。
Cognitoの料金
AWS Cognitoでは月間アクティブユーザー (MAU) に基づいて料金が発生します。その月にサインアップ、サインイン、トークンの更新、パスワードの変更などの操作が発生した場合、そのユーザーはMAUとしてカウントされます。ただしCognitoのユーザープールは月50,000 MAUの無料枠があります。以下は東京リージョンの料金体系になります。
料金範囲 | MAU あたり料金 |
---|---|
50,001 ~ 100,000 (50,000 の無料利用枠以後) | 0.0055USD |
次の 90 万 | 0.0046USD |
次の 900 万 | 0.00325USD |
10,00 万超 | 0.0025USD |
Cognitoの実装コード
Cognitoのインフラ部分をTerraform、ロジック部分をPythonで構築する場合のコードを紹介します。今回はメールアドレスとパスワードでサインインをし、カスタム属性なしのCognitoを作成します。
Terraformでインフラを記述
terraformのコードは次のようになります。
resource "aws_cognito_user_pool" "main" {
name = "cognito-demo-pool"
username_attributes = ["email"]
username_configuration {
case_sensitive = true
}
auto_verified_attributes = ["email"]
password_policy {
minimum_length = 8
require_lowercase = false
require_uppercase = false
require_numbers = false
require_symbols = false
temporary_password_validity_days = 7
}
mfa_configuration = "OFF"
admin_create_user_config {
allow_admin_create_user_only = false
}
account_recovery_setting {
recovery_mechanism {
name = "verified_email"
priority = 1
}
}
}
resource "aws_cognito_user_pool_domain" "main" {
domain = "cognito-demo-pool"
user_pool_id = aws_cognito_user_pool.main.id
}
resource "aws_cognito_user_pool_client" "main" {
name = "cognito-demo-pool-client"
user_pool_id = aws_cognito_user_pool.main.id
generate_secret = false
explicit_auth_flows = [
"ALLOW_USER_PASSWORD_AUTH",
"ALLOW_USER_SRP_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH"
]
prevent_user_existence_errors = "ENABLED"
}
Pythonでロジックを記述
Pythonでロジックを実装するには、boto3というライブラリをインストールする必要があります。
$ pip install boto3
そしてソースコードの冒頭に次のように記述してCognitoのクライアントを作成します。
import boto3
sess = boto3.Session()
client = sess.client(
"cognito-idp",
region_name="ap-northeast-1",
)
新規ユーザー作成
新規ユーザーを作成します。メールアドレス宛に一時的なパスワードを送ります。
def admin_create_user(email: str):
user_attributes = [
{"Name": "email_verified", "Value": "true"},
{"Name": "email", "Value": email},
]
client.admin_create_user(
UserPoolId=POOL_ID,
Username=email,
UserAttributes=user_attributes,
ForceAliasCreation=False,
DesiredDeliveryMediums=["EMAIL"],
)
サインイン
新規ユーザーを作成します。初回サインインの場合はセッションが返され、そうでなければ認証のためのトークンが返却されます。
def user_password_auth(email: str, password: str):
resp = client.initiate_auth(
ClientId=CLIENT_ID,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": email, "PASSWORD": password},
)
if bool(resp["ChallengeParameters"]):
return {"session": resp["Session"]}
else:
return {
"id_token": resp["AuthenticationResult"]["IdToken"],
"access_token": resp["AuthenticationResult"]["AccessToken"],
"refresh_token": resp["AuthenticationResult"]["RefreshToken"],
}
初回サインイン時のパスワード変更
初回サインイン時にセッションと新しいパスワードによりサインインします。トークンが返されます。
def new_password_required(email: str, new_password: str, session: str):
resp = client.respond_to_auth_challenge(
ClientId=CLIENT_ID,
Session=session,
ChallengeName="NEW_PASSWORD_REQUIRED",
ChallengeResponses={"USERNAME": email, "NEW_PASSWORD": new_password},
)
return {
"id_token": resp["AuthenticationResult"]["IdToken"],
"access_token": resp["AuthenticationResult"]["AccessToken"],
"refresh_token": resp["AuthenticationResult"]["RefreshToken"],
}
リフレッシュトークンからトークン再発行
リフレッシュトークンを使ってアクセストークンとIDトークンを再発行します。
def refresh_token_auth(refresh_token: str):
resp = client.initiate_auth(
ClientId=CLIENT_ID,
AuthFlow="REFRESH_TOKEN_AUTH",
AuthParameters={"REFRESH_TOKEN": refresh_token},
)
return {
"id_token": resp["AuthenticationResult"]["IdToken"],
"access_token": resp["AuthenticationResult"]["AccessToken"],
}
パスワード変更
パスワードを変更します。
def change_password(password: str, new_password: str, access_token: str):
"""Change password"""
client.change_password(
PreviousPassword=password,
ProposedPassword=new_password,
AccessToken=access_token,
)
パスワード変更のための確認コード送信
パスワードをリセットするためのメールアドレス宛に確認コードを送信します。
def forgot_password(email: str) -> None:
client.forgot_password(
ClientId=CLIENT_ID,
Username=email
)
パスワードのリセット
確認コードを使って新しいパスワードを設定します。
def confirm_forgot_password(email: str, password: str, code: str) -> None:
client.confirm_forgot_password(
ClientId=CLIENT_ID,
Username=email,
Password=password,
ConfirmationCode=code
)
参考