WHAT YOU’LL LEARN
  • how we can add and deploy a new User Pool and User Pool Domain, via GraphQL API’s cloud infrastructure code
Can I use this?

In order to follow this tutorial, you must use Webiny version 5.18.0 or greater.

The code that we cover in this section can also be found in our GitHub examples repository . Also, if you’d like to see the complete and final code of the application we’re building, check out the full-example folder.

Adding User Pool and User Pool Domain
anchor

In this section, we’ll be performing all of the needed cloud infrastructure code changes within the dev stack. Note that you should perform the same changes within the prod stack as well.

In case you missed it, check out the dev and prod Stacks section.

Let’s start by defining a new User Pool and a User Pool Domain within our GraphQL API’s cloud infrastructure code.

Inside of the pinterest-clone/api/pulumi/dev folder, let’s create the new cognito.ts file. It should export the following Cognito class that encapsulates new cloud infrastructure code:

pinterest-clone/api/pulumi/dev/cognito.ts
import * as aws from "@pulumi/aws";import * as pulumi from "@pulumi/pulumi";import * as crypto from "crypto";
const BASE_ATTRIBUTE_NAMES = ["email", "given_name", "family_name"];
class Cognito {  userPool: aws.cognito.UserPool;  userPoolDomain: aws.cognito.UserPoolDomain;  constructor() {      // Creates a new user pool. For more information on all of the available options, see:      // https://www.pulumi.com/docs/reference/pkg/aws/cognito/userpool/      // https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateUserPool.html      this.userPool = new aws.cognito.UserPool("pinterest-clone", {          schemas: BASE_ATTRIBUTE_NAMES.map(name => ({              name,              attributeDataType: "String",              required: true,              developerOnlyAttribute: false,              mutable: true,              stringAttributeConstraints: {                  maxLength: "2048",                  minLength: "0"              }          })),          passwordPolicy: {              minimumLength: 8,              requireLowercase: false,              requireNumbers: false,              requireSymbols: false,              requireUppercase: false,              temporaryPasswordValidityDays: 7          },          autoVerifiedAttributes: ["email"],          usernameAttributes: ["email"]      });
      // Creates a user pool domain over which we'll be able to access the Hosted UI authentication flow.      // For more information on all of the available options, see:      // https://www.pulumi.com/docs/reference/pkg/aws/cognito/userpooldomain/      // https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateUserPoolDomain.html      this.userPoolDomain = new aws.cognito.UserPoolDomain("pinterest-clone", {          domain: this.userPool.id.apply((id: string) => {              // Create a unique MD5 hash of the User Pool ID and use that for the domain name.              return crypto.createHash("md5").update(id).digest("hex");          }),          userPoolId: this.userPool.id      });  }
  // A simple getter that will construct and return us the complete user pool domain.  getUserPoolDomain() {      return pulumi.interpolate`${this.userPoolDomain.domain}.auth.${process.env.AWS_REGION}.amazoncognito.com`;  }}
export default Cognito;

As we can see, we’re first defining a new User Pool, where we’re configuring some of its essential configuration parameters:

  • schema attributes of the user pool (schemas)
  • password rules (passwordPolicy)
  • enabling verification with a confirmation code (autoVerifiedAttributes)
  • using email attribute as the username (usernameAttributes) .

Then, in order to enable the mentioned Hosted UI authentication flow, we’re also defining a new User Pool Domain, which only takes two configuration parameters - domain and userPoolId. Notice how we’ve assigned `pinterest-clone-${process.env.WEBINY_ENV}` to the former and used process.env.WEBINY_ENV as part of the value. With this approach, we’re simply ensuring that within every environment we deploy into, we’re using a globally unique domain name. We cannot use (or at least shouldn’t) the same domain name within two different environments.

The next step is to import the new file in the existing index.ts entrypoint file, located in the pinterest-clone/api/pulumi/dev folder. Once imported, we’ll also need to perform two additional changes.

First, in order for the new cloud infrastructure code to be actually executed, we’ll instantiate the imported Cognito class. Second, we will modify the list of returned values, which, as previously mentioned, represents the stack output.

Ultimately, this is how the updated index.ts entrypoint file should look like:

pinterest-clone/api/pulumi/dev/index.ts
import DynamoDB from "./dynamoDb";import Graphql from "./graphql";import ApiGateway from "./apiGateway";import Cloudfront from "./cloudfront";import Cognito from "./cognito";
// Among other things, this determines the amount of information we reveal on runtime errors.// https://www.webiny.com/docs/how-to-guides/environment-variables/#debug-environment-variableconst DEBUG = String(process.env.DEBUG);
// Enables logs forwarding.// https://www.webiny.com../../core-development-concepts/basics/watch-command#enabling-logs-forwardingconst WEBINY_LOGS_FORWARD_URL = String(process.env.WEBINY_LOGS_FORWARD_URL);
export default () => {const dynamoDb = new DynamoDB();const cognito = new Cognito();
const api = new Graphql({  dbTable: dynamoDb.table,  env: {    // The single DynamoDB table in which data can be stored and queried.    DB_TABLE: dynamoDb.table.name,    DEBUG,    WEBINY_LOGS_FORWARD_URL,  },});
const apiGateway = new ApiGateway({  routes: [    {      name: "graphql-post",      path: "/graphql",      method: "POST",      function: api.functions.api,    },    {      name: "graphql-options",      path: "/graphql",      method: "OPTIONS",      function: api.functions.api,    },  ],});
const cloudfront = new Cloudfront({ apiGateway });
return {  region: process.env.AWS_REGION,  apiUrl: cloudfront.getDistributionUrl(),  graphqlApiUrl: cloudfront.getDistributionUrl("/graphql"),  dynamoDbTable: dynamoDb.table.name,  cognitoUserPool: {    id: cognito.userPool.id,    domain: cognito.getUserPoolDomain(),  }};};

As names suggest, the cognitoUserPool.id and cognitoUserPool.domain properties represent the ID and the domain of the deployed User Pool and User Pool Domain, respectively. We’ve added these values to the stack output simply because we’ll later need them within our React application code (more on this soon).

Deployment
anchor

In order to continue further development, the new cloud infrastructure resources need to be deployed.

If you had the webiny watch command up and running while making the shown changes, the new cloud infrastructure resources should have been deployed automatically for you:

Automatic Re-deployment of Cloud Infrastructure

If not, you can either start a new watch session, or simply deploy the changes via the webiny deploy command.

Automatic Re-deployment of Cloud Infrastructure

As seen in the screenshot above, except all of the cloud infrastructure resources-related changes, in the Outputs section, we can also see the updated stack output.

Ultimately, both approaches will ensure the new cloud infrastructure resources are deployed and are ready to be used.

Final Result
anchor

In this section, we’ve defined and deployed a new User Pool and a User Pool Domain, all within our GraphQL API’s cloud infrastructure code. Also, by adding the cognitoUserPool.id and cognitoUserPool.domain stack output properties, we’ve exposed the ID and the domain of the deployed User Pool and User Pool Domain. This will enable us to later retrieve these values within our React application code and connect everything together.

In the next section, we’ll still be playing with the cloud infrastructure code, but this time within our React application, where we’ll need to define a new User Pool Client .