Introduction
Amazon Cognito is a user authentication and authorization service provided by AWS. It allows developers to add user sign-up, sign-in, and access control to their web and mobile applications quickly and easily. AWS AppSync is a managed service that makes it easy to develop GraphQL APIs by handling the heavy lifting of securely connecting to data sources like AWS DynamoDB, AWS Lambda, and more.
In this article, we'll explore how to configure two Cognito user pools for a single AppSync GraphQL API using a YAML CloudFormation template. We'll also discuss how to combine the usage of different groups from the two Cognito user pools in the same GraphQL schema.
One of the most interesting aspects of using two Cognito user pools is that you can provide a general authentication system for end users while having a more secure and dynamic mechanism for admin users. With this configuration, you can define different groups of users with different access levels to your API and use them in the same GraphQL schema. By following the steps outlined in this article, you'll be able to create a flexible and secure authentication and authorization system for your AppSync GraphQL API.
There is also a personal addition to this article that explains how we should abstract ourselves from our assumptions and fiercely pursue a learn-by-doing approach to be more effective in problem-solving.
Prerequisites
Before we begin, make sure you have the following prerequisites:
- AWS Account
- AWS CLI installed and configured
- Basic understanding of AWS AppSync and Amazon Cognito
Steps to Configure Two Cognito User Pools for a Single AppSync GraphQL API
1. Create Two Cognito User Pools
The first step is to create two Cognito user pools. Each user pool will represent a different set of users with different access levels. The CloudFormation template for this would be:
Resources:
EndUsersPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: end-users-pool
UsernameAttributes:
- email
AutoVerifiedAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
RequireUppercase: true
Schema:
- AttributeDataType: String
Name: email
Required: true
- AttributeDataType: String
Name: name
Required: false
EndUsersPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: end-users-pool-client
UserPoolId: !Ref EndUsersPool
AdminUsersPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: admin-users-pool
UsernameAttributes:
- email
AutoVerifiedAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
RequireUppercase: true
Schema:
- AttributeDataType: String
Name: email
Required: true
- AttributeDataType: String
Name: name
Required: false
AdminUsersPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: admin-users-pool-client
UserPoolId: !Ref AdminUsersPool
This template creates a Cognito User Pool named "end-users-pool" with email as the username attribute and email as an auto-verified attribute. It also specifies a password policy and defines two user attributes: email and name. It does the same for the admin user pool named “admin-users-pool”.
In addition, the template creates a User Pool Client named "end-users-pool-client" and associates it with the User Pool created in the previous step. Again, it does the same for the admin users pool named "admin-users-pool" and "admin-users-pool-client".
2. Create AppSync GraphQL API
Next, we'll add an AppSync GraphQL API to our YAML CloudFormation template. Here's an example YAML template for creating an AppSync GraphQL API:
Resources:
AppSyncApi:
Type: 'AWS::AppSync::GraphQLApi'
Properties:
Name: graphql-api
AuthenticationType: AMAZON_COGNITO_USER_POOLS
UserPoolConfig:
AwsRegion: us-east-1
DefaultAction: ALLOW
UserPoolIds:
- !Ref EndUsersPool
- !Ref AdminUsersPool
In the YAML template, we define an AppSyncApi resource of type AWS::AppSync::GraphQLApi. We set the Name property to a descriptive name for the API. The AuthenticationType property is set to AMAZON_COGNITO_USER_POOLS to indicate that we'll use Cognito user pools for authentication.
We set the UserPoolConfig property to configure the Cognito user pools for the API. The AwsRegion property specifies the AWS region where the user pools are located. The DefaultAction property sets the default action for users who are not authenticated. The UserPoolIds property is an array of Cognito user pool IDs. In our case, we set it to the IDs of the two user pools we created earlier.
3. Define GraphQL Schema
Next, we'll define the GraphQL schema for our API. In the schema, we'll define two types of users - EndUser and AdminUser - that belong to the two different Cognito user pools.
Here's an example GraphQL schema:
type Query {
endUserQuery: EndUser
adminUserQuery: AdminUser
}
type EndUser @aws_auth(cognito_user_pools: [{userPoolId: "EndUsersPool", groups: ["endUsers"]}]) {
id: ID!
name: String!
}
type AdminUser @aws_auth(cognito_user_pools: [{userPoolId: "AdminUsersPool", groups: ["adminUsers"]}]) {
id: ID!
name:
Possible misleading different syntaxis
You may find a different syntaxis for the same CloudFormation template. The first time I wanted to implement this, I got the following example which uses `@aws_cognito_user_pools` directive with the cognito_groups argument.
type Query {
endUserQuery: EndUser
@aws_cognito_user_pools(cognito_groups: ["endUsers"])
}
Initially, I thought it was misleading because it didn’t use the user pool id as an argument which made me think: “how does it know which Cognito group belongs to which Cognito user pool?” I just decided to try it, and it worked, but it also suggested that if you have two Cognito groups with the same name in both user pools, the results could be unexpected.
Conclusion
Configuring an AppSync GraphQL API with two Cognito User Pools can seem like a daunting task at first, but with the proper steps and considerations, it can be a straightforward and efficient process. By following the steps outlined in this article, including creating and configuring the user pools, setting up the authorization and authentication in AppSync, and testing the API, we can create a secure and reliable API that meets the needs of our application.
Remember to keep security and scalability in mind throughout the process and consult the AWS documentation for additional guidance or troubleshooting.
Also, remember that It is important to know how our internal thought processes can sometimes prevent us from effectively utilizing certain tools or directives when building applications. In the case of the AWS Amplify CLI and the aws_cognito_user_pools directive, the lack of direct reference to the user pool in the directive's groups argument can lead to confusion and hesitation when implementing this feature. This can cause us to second-guess ourselves and miss out on the benefits that the groups argument can provide.
In situations like this, it may be better to adopt a "just try and see what happens" approach rather than being overly cautious. By taking this approach and experimenting with the directive's different arguments, we may discover new ways to use the tool that we may not have considered before. This approach can lead to a more efficient and effective development process and help us better understand the tools we are working with.
In summary, it is essential not to let our internal thought processes limit our ability to utilize powerful tools and features when building applications. With an open-minded and experimental approach, developers can maximize the available resources and create better applications.