How to allow or restrict users of specific domains in AWS Cognito

Restrict users of specific domains in AWS Cognito
Restrict users of specific domains in AWS Cognito

In this article, we're going to discuss how to allow or restrict users of specific domains in AWS Cognito

Why you may want to restrict based on domains?

You may want to build a web application for your company event where you want only the employees of your company to register. One way to identify the employees of your company, he or she will have an email id with your company domain name.

For example, if I'm creating such an application, I would allow anyone whose email address ends with  @cloudtechsimplified.com  

💡
TLDR: You can call a lambda function with pre signup trigger on AWS Cognito. In that lambda function, you can get the email from user attributes who are trying to signup and verify the email address belongs to your company domain.

Infrastructure

We're going to use AWS CDK for creating the necessary infrastructure. It's an open-source software development framework that lets you define cloud infrastructure. AWS CDK supports many languages including TypeScript, Python, C#, Java, and others. You can learn more about AWS CDK from a beginner's guide here. We're going to use TypeScript in this article.

Cognito Pool

Let's create a Cognito Pool as shown below

const cognitoDomainPool = new cognito.UserPool(this, "cognitoDomainPool", {
      userPoolName: "cognito-domain-pool",
      signInCaseSensitive: false, // case insensitive is preferred in most situations
      selfSignUpEnabled: true,
      accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
      mfa: cognito.Mfa.OFF,
      userVerification: {
        emailSubject: "Verify your email for Cogntio Domains App",
        emailBody:
          "Hey, Thanks for signing up to Cognito Domains App. Your verification code is {####}",
        emailStyle: cognito.VerificationEmailStyle.CODE,
      },
      signInAliases: {
        email: true,
        username: false,
      },
      standardAttributes: {
        email: {
          required: true,
        },
      },
    });

Let's add an app client for the pool

const appClient = cognitoDomainPool.addClient("cognito-domain-app-client", {
      userPoolClientName: "cognito-domain-app-client",
      generateSecret: true,
      authFlows: {
        userPassword: true,
      },
      oAuth: {
        flows: {
          authorizationCodeGrant: true,
        },
        callbackUrls: ["http://localhost:3000/api/auth/callback/cognito"],
        logoutUrls: ["http://localhost:3000"],
        scopes: [
          cognito.OAuthScope.EMAIL,
          cognito.OAuthScope.OPENID,
          cognito.OAuthScope.PROFILE,
        ],
      },
    });

Then, we can create a custom domain so that users of our application may recognize the app. Otherwise, it may create subdomain with random characters.

  cognitoDomainPool.addDomain("CognitoDomain", {
      cognitoDomain: {
        domainPrefix: "cts-domain-app-2706",
      },
    });

Lambda function

Let's create a lambda function with pre signup authentication trigger of Cognito. This lambda function will be called when the user tries to sign-up.

 const nodeJsFunctionProps: NodejsFunctionProps = {
      bundling: {
        externalModules: [
          "aws-sdk", // Use the 'aws-sdk' available in the Lambda runtime
        ],
      },
      runtime: Runtime.NODEJS_18_X,
      timeout: Duration.seconds(30), // Api Gateway timeout
    };

    const preSignupAuthLambda = new NodejsFunction(
      this,
      "preSignupAuthLambda",
      {
        entry: "lambdas/pre-signup-auth-lambda.ts",
        ...nodeJsFunctionProps,
        functionName: "cognito-domain-app-pre-signup-auth-lambda",
      }
    );

Configure the pre-sign-up trigger of AWS Cognito to the Lambda function

In the below code snippet, we're going to configure AWS Cognito to add a trigger for the pre-sign-up to the lambda function.  This configuration will make cognito to call our lambda function when a user tries to signup.

  cognitoDomainPool.addTrigger(
      cognito.UserPoolOperation.PRE_SIGN_UP,
      preSignupAuthLambda
    );

Lambda function code

In the actual lambda function code, we're getting the email information from user attributes. And, we're doing a simple check to check whether our email address contains the allowed domain.

export async function handler(event: any, context: any) {
  const userEmailDomain = event.request.userAttributes.email.split("@")[1];
  const allowedDomains = ["cloudtechsimplified.com"];
  if (!allowedDomains.includes(userEmailDomain)) {
    throw new Error("Invalid email domain");
  }
  return event;
}

You can implement any additional logic here.

Deployment & Testing

You can deploy the infrastructure (Cognito & Lambda) by executing cdk deploy command. All the necessary resources would be created.

For testing, I'm creating a simple NextJS application with next-auth library to test our signup process.

I've tried to sign-up using the email someuser@gmail.com - as it does not end with @cloudtechsimplified.com , the error is thrown "Invalid email domain" as shown in the below screenshot.

When you valid email domain - the signup process will complete.

Please let me know your thoughts in the comments.