Controlling access in service-to-service communications with Cognito - Part 1
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 OAuth 2.0 standards and he will use access tokens for authorization.
2. The solution
Bob’s solution starts with Amazon Cognito, 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 API of the other service with the token placed in the
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.
In this example, the User Pool is called
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.
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.
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.
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.
First, we create a resource server called
Test API here. Let’s name the scope Identifier
demo. Then we will define the custom scopes:
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.
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 (
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 Postman to make the token request for now.
4.1. POST request
TOKEN Cognito endpoint only accepts
POST requests. As I mentioned above, we will need to call the
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.
We should also specify the
Content-Type header, whose value must be
application/x-www-form-urlencoded. Postman also has this header built-in.
The body must contain the
grant_type property, which should have the
client_credentials value for service-to-service authorization.
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
The access token is a JWT. 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.
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
OAuth 2.0 - Learn more about OAuth 2.0
Using the access token - Everything about Cognito access tokens
TOKEN endpoint - The endpoint which will return the access token