AWS Cognito M2M Setup with Terraform
Tags: aws, terraform, tutorial, api
Published: 2023-09-15 13:03:33

This guide will walk you through setting up a small prototype with AWS Cognito that can support Machine to Machine Authentication using Terraform as the infrastructure as code language. AWS Cognito is a cost-effective authentication service that offers various features, such as social sign-in, SAML federation, and hosted sign-in pages. However, its documentation has been criticized for being inadequate and not covering important aspects like CloudFormation. Despite this, AWS Cognito remains a powerful tool for managing user identities and access to backend resources.

If you wish to see more updates like this follow me on my  [Linkedin Profile](https://www.linkedin.com/in/michael-jakobsen/)

💰 **Cost Benefit Analysis**
Cognito is at least an order of magnitude cheaper than its competitors such as Auth0 and Okta. This should be a critical consideration when choosing authentication provider.

Cognito Cost:

There is no charge for Cognito Identity Pools. With Amazon Cognito Identity Pools, you can create unique identities and assign permissions for users. You can also sign in users through social identity providers, such as Facebook, Google, or Apple, or through corporate identity providers with SAML or OIDC and control access to your backend resources.

Read more > [AWS Cognito Pricing](https://aws.amazon.com/cognito/pricing/)

📈 **Documentation Quality**

The Burning Monk have said it the best so I will quote him instead of regurgitating what he explained best. 😅

> “It’s simple. The developer experience is terrible.
> Cognito is a really powerful service. It’s capable of a lot of things – SAML federation with other services, social sign-in, hosted sign-in pages, etc. But the documentation on how you accomplish these amazing feats range from barely there to non-existent. And pretty much all of the available documentations involve “open the console, click this button, then click that button”. It’s as if CloudFormation never existed!
> Seriously, why is it that everyone at AWS is constantly telling its customers to follow infrastructure-as-code, only for its documentation to constantly omit CloudFormation?”

If you want to read the rest of his review, please find the link at the bottom of the article.

:blue_book: **Setup Instructions**

**Phase 1: Setup your Cognito Terraform code**

Below is the Terraform code you will need to setup your Cognito User Pool and Cognito User Pool Client.

!! When you do Terraform Apply, you might receive an error due to a sequencing issue, all you have to do is run Terraform Apply again and it should all be setup. !!

**1. Set up User pool**

A user pool is a user directory in Amazon Cognito. It allows you to create and manage user accounts, including sign-up, sign-in, and password recovery.

User Pool creation can be kept pretty basic here, nothing fancy, so we create a User Pool with default settings and we configure it so that only admins can create users:

```
resource ":cognito_user_pool" "cognito_m2m_pool" {
  name = "m2m-pool-prototype"

  admin_create_user_config {
    allow_admin_create_user_only = true
  }
}
```

**2. Set up User pool domain**

In this step, we set up a User Pool domain, which is an endpoint that we can call to fetch tokens from. By creating a User Pool domain, we can customise the domain name to make it more user-friendly and memorable. We can either use a string to be used as a User Pool domain or use our custom domain name.

Important: do not use the word “Cognito”, User pool does not like it.

```
resource "aws_cognito_user_pool_domain" "cognito_m2m_pool_main_domain" {
  domain       = "prototype-m2m-pool-main-domain"
  user_pool_id = aws_cognito_user_pool.cognito_m2m_pool.id
}
```


**3. Client Credentials Grant Type Configurations**

A resource server is an AWS service or resource that your app client can access on behalf of the authenticated user. In this step, you'll enable the resource server for your app client, allowing it to access the necessary AWS resources.

OAuth flow needs a Resource and/or an Authorisation server for generating and/or validating token/code, however as Client Credentials grant type does not have an Authorisation Server, we can create a Resource Server for our User pool. 

For more details on this, check out the developer documentation here - [API authorisation with resource servers and OAuth 2.0 scopes](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html).

Create a resource server and add custom scopes

```
resource "aws_cognito_resource_server" "cognito_m2m_pool_resource_server" {
  # TRY TO ADD DEPENDS ON TO AVOID SEQUENCING CONFLICT WHEN APPLYING
  depends_on   = [aws_cognito_user_pool_domain.cognito_m2m_pool_main_domain]
  identifier   = "auth-resource-server"
  name         = "cognito-m2m-pool-resource-server"
  user_pool_id = aws_cognito_user_pool.cognito_m2m_pool.id

  scope {
    scope_name        = "custom-scope.write"
    scope_description = "Custom scope 1 for M2M prototype"
  }

  scope {
    scope_name        = "custom-scope.read"
    scope_description = "Custom scope 2 for M2M prototype"
  }
}
```


**4. Create an App Client**

An app client represents an application that can authenticate users through the user pool. You'll need to create an app client and configure its settings, such as the allowed OAuth flows and callback URLs.

Add the below to your Terraform resource 

aws_cognito_user_pool_client:

- name
- user_pool_id
- callback_urls
- generate_secret
- allowed_oauth_flows
- allowed_oauth_scopes (this is an array)
- allowed_oauth_flows_user_pool_client
- supported_identity_providers

```
resource "aws_cognito_user_pool_client" "cognito_m2m_pool_client" {
  name                = "example-m2m-pool-prototype-client"
  user_pool_id        = aws_cognito_user_pool.cognito_m2m_pool.id
  callback_urls       = ["https://exampleprototype.com"]
  generate_secret     = true
  allowed_oauth_flows = ["client_credentials"]
  allowed_oauth_scopes = [
    "auth-resource-server/custom-scope.write",
    "auth-resource-server/custom-scope.read"
  ]
  allowed_oauth_flows_user_pool_client = true
  supported_identity_providers         = ["COGNITO"]
}
```

**5. Setup outputs to display your new resources**

Add the below to your Terraform code to output the user_pool_id and the app_client_id.

```
output "cognito_user_pool_id" {
  value = aws_cognito_user_pool.cognito_m2m_pool.id
}

output "cognito_app_client_id" {
  value = aws_cognito_user_pool_client.cognito_m2m_pool_client.id
}

```

**6. Generate Authentication Token**

Generating token now is just as simple as making a POST HTTP Request to our User pool domain at token endpoint i.e. /oauth2/token. 

This request needs an Authorization Header, which is nothing but a baseb4Encode string created with App Client id and Client Secret, custom scopes which we have got from our Resource server.

**Create Authorization header value **

You can use any online tool like this [base64encode](https://www.base64encode.org/) for creating a base64Encode string for a pair of Client Id and Client secret. Simply specify <app_client_id>:<client_secret> and click Encode.

For example, let client_id is 13902u443a9, and client secret is 89u90sdfhhfhfhdihsfldhfsdfsfdfsdjfdslfjhsdlf, then in you create base64Encoded string for this pair separated by colon (:) 

13902u443a9:89u90sdfhhfhfhdihsfldhfsdfsfdfsdjfdslfjhsdlf

**7. Test your Cognito client in Postman or NodeJS
Request Format**

Now that we have everything we need for our request to generate a token, this will be your request format should be:

URL: {your domain name}/oauth2/token

Method: POST

Headers:

Authorization: Basic <base64Encoded string>

Content-Type: application/x-www-form-urlencoded

Body: (x-www-form-urlencoded)

scope: {your custom scopes}

grant_type: client_crendentials

**Conclusion**

We have learned how to set up a small prototype with AWS Cognito, supporting Machine to Machine Authentication using Terraform.

By following this guide, you should now have a basic understanding of how to use AWS Cognito for Machine to Machine Authentication and be able to apply this knowledge to your own projects. Remember to refer to the AWS documentation and other resources for more in-depth information on specific features and configurations.

In our next article we will cover how to setup API Gateway with the User Pool as the Cognito Authoriser. 🤓

**References and Inspiration:**

[The Case for and Against Amazon Cognito by The Burning Monk](https://theburningmonk.com/2021/03/the-case-for-and-against-amazon-cognito/)

[AWS Cognito Authorizer with Client Credentials (Console Setup](https://cloudunfold.com/blog/api-gateway-cognito-authorizer-using-client-credentials/)