Authorization and Amazon Verified Permissions - A New Way to Manage Permissions Part XV: AVP with Cognito groups
Tags: aws, avp, authorization, cognito
Published: 2024-04-27 16:40:16

Welcome back to my blog post series dedicated to building authorization using Cedar and Amazon Verified Permissions. In a previous blog post, we learned about the usage of [AVP getting started](https://dev.to/aws-heroes/authorization-and-amazon-verified-permissions-a-new-way-to-manage-permissions-part-xiv-avp-getting-started-478f). Today, we will cover the topic of using Cognito Groups with AVP. 

Related to AVP getting started, Cognito groups are now supported in the AVP, as you well know from the previous blogpost. However, in this blogpost we won't focus on building a getting started gateway with AVP, but we will focus on building authorization with AVP itself using cognito groups (So you can use it for API gateway, resource server, and enhance it for your use case pretty easily. If you want to see the finished result, you can use the scenario from avp-cli [here](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/ecommerceCognitoGroupsScenario.json).

In addition, we will also use the new [BatchIsAuthorizedWithToken](https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_BatchIsAuthorizedWithToken.html) API operation that was recently released.


## Cognito groups support

Currently, AVP only supports Cognito as an external identity provider(IdP). We create the reference from Cognito through the [CreateIdentitySource](https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_CreateIdentitySource.html) action.

For configuration, we need to specify:
- `policyStoreID`
 - `userPoolArn` (ARN of the Cognito user pool), 
- `clientIds` (optional, unique application client IDs that are associated with the specified Amazon Cognito user pool.)
- `principalEntityType` (namespace and data type for the entity that will be a principal for our policy store)

What was introduced into the [CognitoUserPoolConfiguration](https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_CognitoUserPoolConfiguration.html), that [groupConfiguration](https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_CognitoGroupConfiguration.html) is the entity type that the policy store maps to groups from the Cognito user pool identity source. In short, which entity from the policy store will be mapped from the Cognito group.

Below you can see example values for `principalEntityType` and `groupEntityType` in the case of namespace `MyApp` for the AVP policy store to see how it will look in reality. 
![Explanation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o4vrgl0h2t22aq1o1im5.png)

That is, in short, when building an identity source for a policy store, we need to pass it on, and there you go!

## Practice

Today, we would like to build an authorization system for the banking transaction system. Within, we have a user who can perform operations such as:

- `View`
- `Approve`
- `Edit`
for resources of type `TransactionRecord`.

We would like to introduce here RBAC based on groups:
- `transaction_viewers`
- `transaction_approvers`
- `transaction_editors`
in Cognito, that is, depending on the role, the user can perform different operations.


![System](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1umi71iko659f0bzduhd.png)

## Cognito part

Let's start with our IDP from Cognito, we will create a user pool with groups, app client id, and add users there and add them to groups. Then we will obtain a Cognito token and use it for testing.

In order not to build this from scratch, for the purpose of this blogpost I created a [ready-made Cloudformation template](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/authentication.yaml) that will create a Cognito user pool, groups, and app client, we just need to later create users and add them to groups.

Navigate to the AWS console, and open the AWS console, open Cloudformation, and upload the `authentication.yaml` from the avp-cli repo. 


![CF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3breb0eofpjo9ao4iq4t.png)

After the deployment, as outputs you can see both the `CognitoUserPoolId` as well as `CognitoUserPoolId`.

Navigate to the Cognito user pool.


![Groups](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4ea8rys3scvh1x8qqjz.png)

You will see 3 groups generated, now what you need to do, is create users and assign them to the groups:

![Groups assigment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tel95aj6v2do8jy5xjbq.png)

Later we will need to copy the ARN of the Cognito User Pool, App Client Id (it is called `avp-client`, and initiate the auth with our users.

**I am aware I am showing the cognito user pool, however before publishing the blogpost the cognito user pool on my site will be already deleted. Never share credentials, ARNs, JWTs, ClientIds, or secrets over the internet!**

## Policy Store part

Let's start building a policy store, log in to your AWS console, open the AVP service, and create an empty policy store. 

Go to the schema tab, and paste this [schema](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/schema.json). Save the changes, you should see this action diagram and entity types diagram:


![Actions diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/woo3ove7t482k884k4qt.png)


![Entity types diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zg58jrhnhchekc195ctr.png)

As we can see, we have principal as user (i.e., who), actions like approve, edit, view (i.e., what operation we want to perform), and resource (i.e., on what resource we want to do), i.e., TransactionRecord.

In addition, we can see that we have an entity of type `Group`, to which our user belongs (Hierarchy).

## Identity Source.
Now, we can reference our IDP (so Cognito) with AVP. Navigate to the `Identity sources` in the AVP console and create a new one. Fill the user pool ID (copy it from the user pool details from the Cognito details page, the same for the client application ID (you will find it in the `App integration` section of the user pool).

For the principal details we will use the `Principal` type as `BankingTransationSystem::User` so this will map our user pool to Cognito as a principal. Moreover the principal ID so the token issued for the connected user pool will be mapped to a principal ID in the format ‘<user pool ID>|<sub>’

Unfortunately, we cannot add `groupEntityType` from the AWS console position (we cannot both see the value, modify it, or add it from the console). Although we can do it through AWS-CLI, AWS-SDK, Cloudformation, or through AVP-CLI. 

### AVP CLI for CreateIdentitySource

So if we want to do it via AVP-CLI, we can do it with:

```shell
➜  avp-cli git:(main) node index.js
🚀 Welcome to the AVP CLI Tool!
Designed to streamline your interactions with the Amazon Verified Permissions (AVP) service.
🔧 Create, manage, and delete policy stores, schemas, and policies. Plus, deploy and test with predefined scenarios!
⚠️ Ensure your AWS credentials are correctly set up before proceeding.
? What would you like to do? Manual approach
? What would you like to do? Create Identity Source
? Enter the ID of the policy store TKdiMU5n3DeypqsnEHb9d6
? Enter the entity type of the principal BankingTransactionSystem::User
? Enter the Amazon Cognito User Pool ARN arn:aws:cognito-idp:eu-west-1:ACCOUND_ID_GOES_HERE:userpool/eu-west-1_sXVC4jUDf
? Enter the Amazon Cognito App Client ID 5b33v2gf0cr1c4n6qj04rajjhf
? Enter the entity type for groups, or press enter to skip 
```

Under the hood it will make an SDK call like this:
```shell
{
  policyStoreId: 'TKdiMU5n3DeypqsnEHb9d6',
  principalEntityType: 'BankingTransactionSystem::User',
  configuration: {
    cognitoUserPoolConfiguration: {
      userPoolArn: 'arn:aws:cognito-idp:eu-west-1:ACCOUNT_ID_GOES_HERE:userpool/eu-west-1_sXVC4jUDf',
      clientIds: [Array],
      groupConfiguration: [Object]
    }
  }
}
```
In the console you will see it like:

![Identity Source details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xy6xpbs6yyoyouy4yqeb.png)

Hopefully, soon we will see the group over there.

## Policies

Now it's time to add [three access policies](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/allow_policy_1.cedar), for each action separately, it will look like this:

```bash
permit (
    principal in
        BankingTransactionSystem::Group::"<user-pool-id-goes-here>|<cognito-group-name-here>",
    action in [BankingTransactionSystem::Action::"View"],
    resource
);
```
What we see is:

- we check if the principal is (with `in` operator which checks hierarchies) in a specific group (in the above example `transaction_viewers`) from the Cognito user pool.
- we check a specific action
- resource is any.

We need to add it for all three groups (per action) accordingly, and it will look like this:

![Policy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uue4zyhoq592xu94l5cn.png).

Feel free to add those three policies from the AVP-CLI repo, remember to replace `<user-pool-id-goes-here>` with your Cognito user pool ID (not the ARN!)

**Remember about `|` between cognito user pool id and group name from the Cognito!**


## Approach with AVP-CLI scenario

As you can see, a little has to be done to get everything ready. What you can do more simply is to use avp-cli to do the deployment of all the things you need.

Remember to first do the deployment of the [authentication.yml](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/authentication.yaml) file in the Cloudformation console if you haven't done it before.

Next, simply use the AVP-CLI:

```shell
➜  avp-cli git:(main) ✗ AWS_PROFILE=personal node index.js
🚀 Welcome to the AVP CLI Tool!
Designed to streamline your interactions with the Amazon Verified Permissions (AVP) service.
🔧 Create, manage, and delete policy stores, schemas, and policies. Plus, deploy and test with predefined scenarios!
⚠️ Ensure your AWS credentials are correctly set up before proceeding.
? What would you like to do? Use prepared scenarios
? Choose a scenario Ecommerce with Cognito Groups Scenario
? Enter the ARN of the Cognito User Pool arn:aws:cognito-idp:eu-west-1:ACCOUNT_NUMBER:userpool/eu-west-1_sXVC4jUDf
? Enter the App Client ID 5b33v2gf0cr1c4n6qj04rajjhf
./scenarios/ecommerceCognitoGroupsScenario/ecommerceCognitoGroupsScenario.json
Starting creating scenario: Ecommerce Cognito Group Scenario
description: This is a basic scenario with a group cognito management platform schema and three policies.
Policy store created with ID: Ny9v75vt5cQGxzEovBgL9R
Schema put successfully for policy store ID: Ny9v75vt5cQGxzEovBgL9R
Creating an identity source...
Identity source created with ID: 9M6FTv52wSnLTR2LAJkRYa
Creating a static policy...
Static policy created with ID: W4Z31yfapD5jWJqvXr57XH
Creating a static policy...
Static policy created with ID: 5k1QXfuWxYJqQa9r3Mnygh
Creating a static policy...
Static policy created with ID: WDu7Pac7umBpHrY2o55TYF
┌────────────────────────────────────────┬────────────────────────────────────────┬────────────────────────────────────────┐
│ Policy ID                              │ Policy Store ID                        │ Created Date                           │
├────────────────────────────────────────┼────────────────────────────────────────┼────────────────────────────────────────┤
│ W4Z31yfapD5jWJqvXr57XH                 │ Ny9v75vt5cQGxzEovBgL9R                 │ 2024-04-27 18:00                       │
├────────────────────────────────────────┼────────────────────────────────────────┼────────────────────────────────────────┤
│ 5k1QXfuWxYJqQa9r3Mnygh                 │ Ny9v75vt5cQGxzEovBgL9R                 │ 2024-04-27 18:00                       │
├────────────────────────────────────────┼────────────────────────────────────────┼────────────────────────────────────────┤
│ WDu7Pac7umBpHrY2o55TYF                 │ Ny9v75vt5cQGxzEovBgL9R                 │ 2024-04-27 18:00                       │
└────────────────────────────────────────┴────────────────────────────────────────┴────────────────────────────────────────┘
Generating of the undefined is finished. Open the AWS console to play around with that.

Consider testing it with our prepared test scenarios:
Either use `Test Scenario` in main CLI to select the specific test scenario, or use below path as argument to `IsAuthorized` from the manual approach option of the CLI:
 Remember to update the policy-store-id within files.
- ./scenarios/ecommerceCognitoGroupsScenario/allow_test_1.json (Access for user who has transaction_viewer group can view) allow
- ./scenarios/ecommerceCognitoGroupsScenario/allow_test_2.json (Access for user who has transaction_approve group can approve) allow
- ./scenarios/ecommerceCognitoGroupsScenario/allow_test_3.json (Access for user who has transaction_edit group can edit) allow

Generating of the ecommerceCognitoGroupsScenario is finished. Open the AWS console to play around with that.
```
And that's it, you can test.

## Prepration for tests

First, we need an access token (or ID token) from Cognito for our user.

Remember, when you create a new user, you must generate a new password for them, which can be done in the AWS CLI with:

```shell
aws cognito-idp admin-respond-to-auth-challenge 
```
You can generate code via AWS-CLI, SDK, or by doing the HTTP request via POSTMAN or curl like this:

```shell
curl --location 'https://cognito-idp.<REGION>.amazonaws.com/<USER_POOL_HERE!!!!!>' \
--header 'Content-Type: application/x-amz-json-1.1' \
--header 'Accept: */*' \
--header 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
--data-raw '{
  "AuthFlow": "USER_PASSWORD_AUTH",
  "ClientId": "<ID_HERE>",
  "AuthParameters": {
    "USERNAME": "<USERNAME>",
    "PASSWORD": "<PASSWORD>"
  }
}
'
```
As a response, you will obtain both an access token and an id token, use the access token.

If you decode the token with jwt.io you will see:

```JSON
{
  "sub": "sub",
  "cognito:groups": [
    "transaction_viewers"
  ],
  "iss": 'BLABLA",
  ...
  "auth_time": 1713899648,
  "exp": 1713903248,
  "iat": 1713899648,`
  "username": "daniel"
}
```
so you can double check at this point, whether a proper group is within the JWT of a user.

## Testing
So we can't test it using the test bench because it doesn't support isAuthorizedWithToken operations and for Batch, so we can do it through AWS CLI, SDK, or through AVP-CLI.

### With isAuthorizedWithToken

For authorization with a token, we will use the [isAuthorizedWithToken](https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_IsAuthorizedWithToken.html) operation.

We can provide both an access token and an ID token, we will use the access token. It is worth reminding that our principal is a user from the Cognito user pool. AVP on its side will decode the token and check if it is ok, so we don't need to do it on our side.

**If you delete an Amazon Cognito user pool or user, tokens from that deleted pool or that deleted user continue to be usable until they expire.
**

The structure of the request is [here](https://github.com/Pigius/avp-cli/blob/main/structureAuthorizationWithTokenRequest.json). For use usecase we can use this [allow test](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/allow_test_1.json). Generate the access token, and pass it together with policy store ID.

So I've generated an access token for a user which is in the `transaction_viewers` group, and based on the allow test I will try to access the `View` action.

now we will use AVP-CLI:

```shell
? What would you like to do? Manual approach
? What would you like to do? Make an authorization decision with Cognito Identity Token
? Enter the path for json test file scenarios/ecommerceCognitoGroupsScenario/allow_test_1.json

┌──────────┬──────────────────────────────┬────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
│ Decision │ Determining Policies         │ Errors             │ Policy Store ID              │ Principal                    │ Action                       │ Resource                     │ Context                      │
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ ALLOW    │ 7UQxoV3bvzpYvGnm939HZH       │                    │ TKdiMU5n3DeypqsnEHb9d6       │ BankingTransactionSystem::Us │ BankingTransactionSystem::Ac │ BankingTransactionSystem::Tr │ {}                           │
│          │                              │                    │                              │ er::eu-west-1_sXVC4jUDf|f245 │ tion::View                   │ ansactionRecord::*           │                              │
│          │                              │                    │                              │ a454-90c1-7084-7709-1dfbfdb1 │                              │                              │                              │
│          │                              │                    │                              │ 0930                         │                              │                              │                              │

```

If I will try the second test scenario I will be rejected, as my user is only allowed to view.

### With BatchIsAuthorizedWithToken

As recently [BatchIsAuthorizedWithToken](https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_BatchIsAuthorizedWithToken.html) has been released, we can use it to test our scenario.

With this operation, we can make up to 30 requests during a single request (and we paid per request, and performance is better). Note that the current request limit is 30, and also our batch can contain up to 100 resources and up to 99 user groups. Example structure is [here](https://github.com/Pigius/avp-cli/blob/main/structureBatchAuthorizationWithTokenRequest.json).

We can pass either an access token or an ID token. The request is either principal or resource oriented.

So, in our scenario, what we can do, is to check, whether my user can access multiple actions on a single resource by making one request to AVP.


![Batch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ibjhssbay8ss7vn7x6q1.png)



I've prepared test file for that [here](https://github.com/Pigius/avp-cli/blob/main/scenarios/ecommerceCognitoGroupsScenario/batch_allow_test.json).

Let's try with AVP-CLI now:


```
➜  avp-cli AWS_PROFILE=personal node index.js
🚀 Welcome to the AVP CLI Tool!
Designed to streamline your interactions with the Amazon Verified Permissions (AVP) service.
🔧 Create, manage, and delete policy stores, schemas, and policies. Plus, deploy and test with predefined scenarios!
⚠️ Ensure your AWS credentials are correctly set up before proceeding.
? What would you like to do? Manual approach
? What would you like to do? Make a batch authorization decision with Cognito Identity Token
? Enter the path for batch authorization with token json test file scenarios/ecommerceCognitoGroupsScenario/batch_allow_test.json
Making batch with token authorization decision...

┌──────────┬──────────────────────────────┬────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
│ Decision │ Determining Policies         │ Errors             │ Policy Store ID              │ Principal                    │ Action                       │ Resource                     │ Context                      │
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ ALLOW    │ 7UQxoV3bvzpYvGnm939HZH       │                    │ TKdiMU5n3DeypqsnEHb9d6       │ BankingTransactionSystem::Us │ BankingTransactionSystem::Ac │ BankingTransactionSystem::Tr │ {}                           │
│          │                              │                    │                              │ er::eu-west-1_sXVC4jUDf|f245 │ tion::View                   │ ansactionRecord::123         │                              │
│          │                              │                    │                              │ a454-90c1-7084-7709-1dfbfdb1 │                              │                              │                              │
│          │                              │                    │                              │ 0930                         │                              │                              │                              │
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ DENY     │                              │                    │ TKdiMU5n3DeypqsnEHb9d6       │ BankingTransactionSystem::Us │ BankingTransactionSystem::Ac │ BankingTransactionSystem::Tr │ {}                           │
│          │                              │                    │                              │ er::eu-west-1_sXVC4jUDf|f245 │ tion::Edit                   │ ansactionRecord::123         │                              │
│          │                              │                    │                              │ a454-90c1-7084-7709-1dfbfdb1 │                              │                              │                              │
│          │                              │                    │                              │ 0930                         │                              │                              │                              │
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ DENY     │                              │                    │ TKdiMU5n3DeypqsnEHb9d6       │ BankingTransactionSystem::Us │ BankingTransactionSystem::Ac │ BankingTransactionSystem::Tr │ {}                           │
│          │                              │                    │                              │ er::eu-west-1_sXVC4jUDf|f245 │ tion::Approve                │ ansactionRecord::123         │                              │
│          │                              │                    │                              │ a454-90c1-7084-7709-1dfbfdb1 │                              │                              │                              │
│          │                              │                    │                              │ 0930                         │                              │                              │                              │
└──────────┴──────────────────────────────┴──────────
```
We will obtain the array of authorization request decisions. As you can see, my user is only allowed to `View`.


## Sumup

That's it for today. We now know:
- how to build AVP policy stores with Cognito as IDP
- use Cognito groups within the access policy
- how to test it.

Now go build!