Amazon Cognito User Pool でサインアップ可能なメールアドレスのドメインを制限してみた

Amazon Cognito User Pool でサインアップ可能なメールアドレスのドメインを制限してみた

社内向けシステムや特定組織向けのアプリケーションの認証を IdP などに頼らず手軽に構築できます。
2026.04.23

こんにちは、製造ビジネステクノロジー部の若槻です。

Amazon Cognito User Pools では、デフォルトではどのメールアドレスでもサインアップが可能です。社内向けシステムや特定組織向けのアプリケーションを構築する際には、特定ドメイン(例: @classmethod.jp)のメールアドレスのみサインアップを許可したいケースがあります。

今回は、Pre-Signup Lambda トリガーを使ってサインアップ可能なメールアドレスのドメインを制限する方法を、AWS CDK で実装・検証してみました。

マネージドログインの有効化については以下の記事も合わせてご参照ください。

AWS CDK の L2 Construct で Amazon Cognito User Pool のマネージドログインが有効化可能になりました

構成

以下のリソースを AWS CDK で構築します。

  • Cognito User Pool: メールアドレスをサインインエイリアスとして使用、セルフサインアップ有効
  • Pre-Signup Lambda: サインアップ時にメールアドレスのドメインを検証し、許可ドメイン以外はエラーを返す
  • Pre-Authentication Lambda: サインイン時にメールアドレスのドメインを検証し、許可ドメイン以外はエラーを返す
  • Cognito Domain + マネージドログイン: 動作確認用の UI

実装

Lambda ハンドラー

sample-handler.ts
import { PreSignUpTriggerEvent } from "aws-lambda";

const ALLOWED_DOMAINS = (process.env.ALLOWED_DOMAINS ?? "classmethod.jp").split(",");

export const handler = async (
  event: PreSignUpTriggerEvent,
): Promise<PreSignUpTriggerEvent> => {
  const email = event.request.userAttributes.email;
  const domain = email.split("@")[1];

  if (!ALLOWED_DOMAINS.includes(domain)) {
    throw new Error(
      `このメールアドレスではサインアップできません。利用可能なドメイン: ${ALLOWED_DOMAINS.join(", ")}`,
    );
  }

  return event;
};

ALLOWED_DOMAINS 環境変数にカンマ区切りで許可するドメインを指定します。メールアドレスの @ 以降のドメイン部分を取り出し、許可リストに含まれていない場合はエラーをスローします。

Cognito の Pre-Signup トリガーではエラーをスローすることで、サインアップを中断させることができます。

CDK スタック

sample-stack.ts
import * as cdk from "aws-cdk-lib";
import * as cognito from "aws-cdk-lib/aws-cognito";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";

const COGNITO_DOMAIN_PREFIX = "wakatsuki-email-domain-20260422";
const CALLBACK_URLS = ["https://dev.classmethod.jp/"];
const LOGOUT_URLS = ["https://dev.classmethod.jp/"];
const ALLOWED_DOMAINS = ["classmethod.jp"];

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    /**
     * Pre-Signup Lambda トリガー: サインアップ時にメールアドレスのドメインを制限する
     */
    const preSignUpLambda = new lambdaNodejs.NodejsFunction(
      this,
      "PreSignUpLambda",
      {
        entry: "../server/src/lambda/handlers/sample-handler.ts",
        runtime: lambda.Runtime.NODEJS_22_X,
        environment: {
          ALLOWED_DOMAINS: ALLOWED_DOMAINS.join(","),
        },
      },
    );

    /**
     * Pre-Authentication Lambda トリガー: サインイン時にメールアドレスのドメインを制限する
     * サインアップ後にメールアドレス(エイリアス)を変更した場合のドメイン制限に対応
     */
    const preAuthenticationLambda = new lambdaNodejs.NodejsFunction(
      this,
      "PreAuthenticationLambda",
      {
        entry: "../server/src/lambda/handlers/sample-handler.ts",
        handler: "preAuthenticationHandler",
        runtime: lambda.Runtime.NODEJS_22_X,
        environment: {
          ALLOWED_DOMAINS: ALLOWED_DOMAINS.join(","),
        },
      },
    );

    /**
     * Cognito ユーザープール
     * - メールアドレスをサインインエイリアスとして使用
     * - Pre-Signup トリガーでサインアップ可能なドメインを制限
     * - Pre-Authentication トリガーでサインイン時のドメインを制限
     */
    const userPool = new cognito.UserPool(this, "UserPool", {
      signInAliases: {
        email: true,
      },
      autoVerify: {
        email: false,
      },
      selfSignUpEnabled: true,
      lambdaTriggers: {
        preSignUp: preSignUpLambda,
        preAuthentication: preAuthenticationLambda,
      },
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    /**
     * マネージドログインを有効化した Cognito ドメイン
     */
    userPool.addDomain("UserPoolDomain", {
      cognitoDomain: { domainPrefix: COGNITO_DOMAIN_PREFIX },
      managedLoginVersion: cognito.ManagedLoginVersion.NEWER_MANAGED_LOGIN,
    });

    /**
     * Cognito ユーザープールクライアント
     */
    const client = userPool.addClient("UserPoolClient", {
      oAuth: {
        callbackUrls: CALLBACK_URLS,
        logoutUrls: LOGOUT_URLS,
        flows: { authorizationCodeGrant: true },
        scopes: [
          cognito.OAuthScope.EMAIL,
          cognito.OAuthScope.PROFILE,
          cognito.OAuthScope.OPENID,
        ],
      },
    });

    /**
     * マネージドログインのデフォルトスタイルを適用
     */
    const managedLoginBranding = new cognito.CfnManagedLoginBranding(
      this,
      "ManagedLoginBranding",
      {
        userPoolId: userPool.userPoolId,
        clientId: client.userPoolClientId,
        useCognitoProvidedValues: true,
      },
    );

    managedLoginBranding.node.addDependency(userPool);
    managedLoginBranding.node.addDependency(client);
  }
}

同じファイルに複数のハンドラーを定義している場合、NodejsFunctionhandler プロパティでエクスポート名を指定できます。デフォルト値は "handler" です。

UserPoollambdaTriggers に両方の Lambda を渡すだけでトリガーとして登録されます。

動作確認

Pre-Signup トリガー(サインアップ時)

AWS CLI を使って動作を確認します。

許可されていないドメイン(@gmail.com)でサインアップ:

aws cognito-idp sign-up \
  --client-id <client-id> \
  --username testuser@gmail.com \
  --password "TestPass123!" \
  --user-attributes Name=email,Value=testuser@gmail.com
An error occurred (UserLambdaValidationException) when calling the SignUp operation:
PreSignUp failed with error このメールアドレスではサインアップできません。利用可能なドメイン: classmethod.jp.

Pre-Signup Lambda がエラーを返し、サインアップが拒否されました。

許可されているドメイン(@classmethod.jp)でサインアップ:

aws cognito-idp sign-up \
  --client-id <client-id> \
  --username testuser@classmethod.jp \
  --password "TestPass123!" \
  --user-attributes Name=email,Value=testuser@classmethod.jp
{
  "UserConfirmed": false,
  "UserSub": "c744daf8-d061-7050-f136-aca542b3a449"
}

@classmethod.jp ドメインでは正常にサインアップできました。

マネージドログインの Sign up ページでも同様に確認できます。


マネージドログインの Sign up ページ

testuser@gmail.com とパスワードを入力して「Sign up」ボタンをクリックすると、以下のエラーが表示されます。


@gmail.com でサインアップしようとするとエラーが表示される

Pre-Authentication トリガー(サインイン時)

メールアドレスの変更を可能としている場合、Pre-Signup トリガーだけではドメイン制限を回避できてしまいます。Pre-Authentication トリガーを追加することでサインイン時にもドメインを検証できます。

sample-handler.tspreAuthenticationHandler を追記します。

sample-handler.ts
import { PreAuthenticationTriggerEvent, PreSignUpTriggerEvent } from "aws-lambda";

const ALLOWED_DOMAINS = (process.env.ALLOWED_DOMAINS ?? "classmethod.jp").split(",");

export const handler = async (
  event: PreSignUpTriggerEvent,
): Promise<PreSignUpTriggerEvent> => {
  // ...(Pre-Signup の実装)
};

/**
 * Pre-Authentication トリガー: サインイン時にメールアドレスのドメインを検証する
 * サインアップ後にメールアドレス(エイリアス)を変更した場合のドメイン制限に対応
 */
export const preAuthenticationHandler = async (
  event: PreAuthenticationTriggerEvent,
): Promise<PreAuthenticationTriggerEvent> => {
  const email = event.request.userAttributes.email;
  const domain = email.split("@")[1];

  if (!ALLOWED_DOMAINS.includes(domain)) {
    throw new Error(
      `このメールアドレスではサインインできません。利用可能なドメイン: ${ALLOWED_DOMAINS.join(", ")}`,
    );
  }

  return event;
};

CDK スタックでは NodejsFunctionhandler プロパティでエクスポート名を指定し、lambdaTriggers.preAuthentication に渡します。

sample-stack.ts
const preAuthenticationLambda = new lambdaNodejs.NodejsFunction(
  this,
  "PreAuthenticationLambda",
  {
    entry: "../server/src/lambda/handlers/sample-handler.ts",
    handler: "preAuthenticationHandler",
    runtime: lambda.Runtime.NODEJS_22_X,
    environment: {
      ALLOWED_DOMAINS: ALLOWED_DOMAINS.join(","),
    },
  },
);

const userPool = new cognito.UserPool(this, "UserPool", {
  // ...
  lambdaTriggers: {
    preSignUp: preSignUpLambda,
    preAuthentication: preAuthenticationLambda,
  },
});

@classmethod.jp でサインアップしたユーザーのメールアドレスを @gmail.com に変更してからサインインを試みます。@classmethod.jp でサインアップしたユーザーのメールアドレスを @gmail.com に変更してからサインインを試みます。

メールアドレスを @gmail.com に変更後、サインインを試みる:

aws cognito-idp admin-initiate-auth \
  --user-pool-id <user-pool-id> \
  --client-id <client-id> \
  --auth-flow ADMIN_USER_PASSWORD_AUTH \
  --auth-parameters USERNAME=testuser@gmail.com,PASSWORD="TestPass123!"
An error occurred (UserLambdaValidationException) when calling the AdminInitiateAuth operation:
PreAuthentication failed with error このメールアドレスではサインインできません。利用可能なドメイン: classmethod.jp.

Pre-Authentication Lambda がエラーを返し、サインインが拒否されました。

メールアドレスを @classmethod.jp に戻してサインイン:

aws cognito-idp admin-initiate-auth \
  --user-pool-id <user-pool-id> \
  --client-id <client-id> \
  --auth-flow ADMIN_USER_PASSWORD_AUTH \
  --auth-parameters USERNAME=testuser@classmethod.jp,PASSWORD="TestPass123!"
{
  "AuthenticationResult": {
    "AccessToken": "...",
    "ExpiresIn": 3600,
    ...
  }
}

@classmethod.jp ドメインでは正常にサインインできました。

まとめ

Cognito User Pool の Lambda トリガーを使うことで、サインアップ・サインインの両方でメールアドレスのドメインを制限できました。

トリガー タイミング ユースケース
Pre-Signup サインアップ時 新規登録を特定ドメインに限定する
Pre-Authentication サインイン時 メールアドレス変更後のサインインを制限する

メールアドレス変更を許可しているユースケースでは、両トリガーを組み合わせることでより堅牢なドメイン制限が実現できます。

AI を活用したアプリケーション開発が手軽になった今だからこそ、セキュリティもセットで意識していきたいですね。

以上

この記事をシェアする

関連記事