Traffine I/O

日本語

2022-10-16

AWS Cognitoの使い方

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
    )

参考

https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html
https://aws.amazon.com/cognito/pricing/?nc1=h_ls
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!