Controlling access in service-to-service communications with Cognito - Part 1
Tags: aws, security, cognito, microservices
Published: 2022-05-12 08:38:42

We can use Cognito to control access in service-to-service communications. It helps us build secure microservices and make it possible to implement granular permissions for our API.

## 1. The scenario

Bob's engineering team is in the middle of splitting up their monolith service into microservices. Each microservice exposes an API, and Bob must figure out how to control access when one service calls another.

Bob decided to follow the <a href="https://www.oauth.com/" class="link" target="_blank" rel="noopener noreferrer">OAuth 2.0</a> standards and he will use **access tokens** for authorization.

## 2. The solution

Bob's solution starts with <a href="https://aws.amazon.com/cognito/" class="link" target="_blank" rel="noopener noreferrer">Amazon Cognito</a>, which will generate the access tokens. The service that wants to access the other microservice will call Cognito and receive the token in the response (covered in this post).

Next the service will make a request to the <a href="https://aws.amazon.com/api-gateway/" class="link" target="_blank" rel="noopener noreferrer">API</a> of the other service with the token placed in the `Authorization` header.

The API Gateway in the called service will decode the token and verify if it contains the necessary permissions called **scopes**. If the scope in the token matches one of the required permissions Bob defined in the API Gateway authorizer, the request will continue to the back-end. If the access token proves to be invalid or doesn't have the required permissions, API Gateway will deny the request. I will cover this part in the next post.

## 3. Steps to set up the Cognito side

Bob quickly set up a proof-of-concept in Cognito to receive the access token he wants to use for authorization.

To do it, he followed the steps described below. The screenshots are from the old Cognito console.

### 3.1. Create a User Pool

We need a **User Pool** first. We don't need any users because we will create access tokens for service-to-service communication. We can accept the default settings here.

![User pool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqqcivei0chjjx3pjb5x.png)

In this example, the User Pool is called `s2s-test`.

All settings below will belong to the User Pool we created in this step.

### 3.2. Create an App client

The **App client** will call Cognito on behalf of the microservice which wants to make the authorized request to the other microservice.

We have to create an App client in Cognito. We give it a name (in this example, `sts-test-app-client`) and accept the default settings. We must keep the **Generate client secret** box checked because we'll need to specify it in the request for the access token.

![Generate an App client](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ve8qac9h7p5ub1qugct9.png)

The default token expiration is one hour, and we can modify it if we want to.

We will need the `App client id` and `App client secret` strings later. When the calling service wants to generate an access token, it will provide the App client IDs to Cognito. This way, Cognito will know that the service behind the App client is legit, and it can safely return the token.

![Client ID and secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5c7gx0y5oscc4v2pez9z.png)

We can have more than one App client for a User Pool. We can create different apps for different platforms or services, for example.

### 3.3. Domain name

We will need a URL we can invoke to get the access token.

Let's go to the **Domain name** section, and create a Cognito domain. The calling service will invoke the `https://<DOMAIN_NAME>/oauth2/token` URL with the App client ID and secret we created in 3.2.

![Domain name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq7ryf7nbzssl7tnhftq.png)

We can have our own domain too, which we must configure in <a href="https://aws.amazon.com/route53/" class="link" target="_blank" rel="noopener noreferrer">Route 53</a>. Custom domains also require a public certificate, which we can get free from <a href="https://aws.amazon.com/certificate-manager/" class="link" target="_blank" rel="noopener noreferrer">ACM</a>.

### 3.4. Resource server and custom scopes

Next, we will set up the custom **scopes** in the **Resource servers** section.

Scopes are permissions. We can use these scopes to ensure fine-grained access control to other services.

![Resource server and custom scopes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87pzggvvf7i36mc7caxo.png)

First, we create a **resource server** called `Test API` here. Let's name the scope **Identifier** `demo`. Then we will define the custom scopes: `read.file` and `write.file`. The final format of the scope strings will be `RESOURCE_SERVER_IDENTIFIER/SCOPE_NAME` because Cognito will concatenate the resource server name and the scope with a `/` in between.

### 3.5. Set grant type in App client

The last step is to set up the **grant type** in the **App client settings** menu.

![Grant type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/naep1qjfhoygbvyfhi5v.png)

The grant type should be `client_credentials` for service-to-service communication, so we want to check the **Client credentials** box in the Allowed OAuth Flows section. We also want to tick off the boxes next to the custom scopes we have created in the previous step (`demo/write.file` and `demo/read.file`).

When we invoke the Cognito domain with the App client credentials, we can request that the custom scopes be in the access token. We can't have any permissions in the access token that we haven't listed in the Allowed Custom Scope section. The Cognito API won't respond with an error if we require a scope that is not allowed. The access token won't contain that permission instead.

## 4. Action time

We are now ready to create the access token!

I'll use <a href="https://www.postman.com/" class="link" target="_blank" rel="noopener noreferrer">Postman</a> to make the token request for now.

### 4.1. POST request

The `TOKEN` Cognito endpoint only accepts `POST` requests. As I mentioned above, we will need to call the `https://<COGNITO_DOMAIN>/oauth2/token` endpoint.

### 4.2. Headers

We must pass the App client ID and secret in a Base64-encoded format to the `Authorization` header. Luckily, Postman automatically sets it up for us.

![Basic authorization in Postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7vg4am0319tg34xrljrj.png)

We should also specify the `Content-Type` header, whose value must be `application/x-www-form-urlencoded`. Postman also has this header built-in.

### 4.3. Body

The body must contain the `grant_type` property, which should have the `client_credentials` value for service-to-service authorization.

![Request body](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pj3b3qpifbcwh6pgjr3a.png)

We can specify which scopes (permissions) we want to have in the access token. The `scope` will only be there if we have previously enabled it in the App client (3.5).

### 4.4. Access token

We can now hit the **Send** button, and we should receive an object with `access_token`, `expires_in`, and `token_type` properties.

The access token is a <a href="https://jwt.io/" class="link" target="_blank" rel="noopener noreferrer">JWT</a>. If we inspect the token, we will see the required (and allowed) scopes there. The called service will validate if the required scopes are in the token.

## 5. Summary

We can use the `client_credentials` grant type to generate access tokens for service-to-service communication. The calling service will receive it from Cognito upon submitting a request to the `TOKEN` endpoint. The called microservice will then validate the token with Cognito.

Part 2 will cover how Bob can validate the token in the other service.

## 6. References and further learning

<a href="https://www.oauth.com/" class="link" target="_blank" rel="noopener noreferrer">OAuth 2.0</a> - Learn more about OAuth 2.0

<a href="https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html" class="link" target="_blank" rel="noopener noreferrer">Using the access token</a> - Everything about Cognito access tokens

<a href="https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html" class="link" target="_blank" rel="noopener noreferrer">TOKEN endpoint</a> - The endpoint which will return the access token