LocalStack LogoLocalStack Icon

How to test Cognito Integration with Amazon Verified Permissions using LocalStack

LocalStack now supports Amazon Verified Permissions (AVP), allowing you to test your application's authorization logic and Cognito integration entirely on your local machine. This tutorial shows how to use Cedar policies to validate access control without deploying to AWS.

How to test Cognito Integration with Amazon Verified Permissions using LocalStack

Introduction

Amazon Verified Permissions (AVP) lets you manage access control separately from your application using external policies. It works with identity providers like Amazon Cognito by using Identity Sources, so your app can use JWTs from Cognito in authorization requests. AVP uses the token to identify the principal of the request and maps the claims to Cedar-specific values. The ID token claims get mapped to principal attributes and access token claims map to the request context, to decide if access should be allowed.

Testing the integration between your app logic, Cognito identities, and AVP policies usually requires deploying everything to AWS. This can make development slower and add costs during repeated testing and debugging.

This is where LocalStack comes in—Amazon Verified Permissions (AVP) is a new service provider in LocalStack’s core cloud emulator. It allows you to use the AVP APIs locally to test your authorization logic and integration points. This tutorial will walk you through:

  • Creating a Cognito User Pool, User, and Group
  • Setting up an AVP Policy Store and Identity Source connected to Cognito
  • Writing a Cedar policy and testing an authorization request with a Cognito ID token

… all within your local LocalStack environment! Let’s dive right in, shall we?

Prerequisites

Step 1: Start LocalStack

Before creating any resources, ensure your LocalStack container is running. To use AVP, make sure your Auth Token is configured.

Terminal window
localstack auth set-token <YOUR_LOCALSTACK_AUTH_TOKEN>
localstack start

Throughout this tutorial, we will use awslocal, a wrapper around the standard aws CLI that automatically directs commands to your LocalStack container.

Step 2: Create a Cognito UserPool

First, create a Cognito User Pool using the CreateUserPool API. This pool will manage the users for our test application. The following command creates a user pool named avp-test:

Terminal window
USER_POOL_OUTPUT=$(awslocal cognito-idp create-user-pool \
--pool-name avp-test \
--output json)
USER_POOL_ID=$(echo $USER_POOL_OUTPUT | jq -r '.UserPool.Id')
USER_POOL_ARN=$(echo $USER_POOL_OUTPUT | jq -r '.UserPool.Arn')
echo "UserPool ID: $USER_POOL_ID"
echo "UserPool ARN: $USER_POOL_ARN"

The Id and Arn are extracted from the output using jq and made available as USER_POOL_ID and USER_POOL_ARN for upcoming commands.

Step 3: Create a User Pool Client

Next, create a User Pool Client. This client represents your application and allows it to interact with the User Pool (e.g., authenticate users).

Use the CreateUserPoolClient API, using the USER_POOL_ID created in the previous step:

Terminal window
CLIENT_OUTPUT=$(awslocal cognito-idp create-user-pool-client \
--user-pool-id "$USER_POOL_ID" \
--client-name avp-client \
--output json)
CLIENT_ID=$(echo $CLIENT_OUTPUT | jq -r '.UserPoolClient.ClientId')
echo "Client ID: $CLIENT_ID"

The new client’s ClientId will be accessible via the CLIENT_ID variable in the next steps.

Step 4: Create a Cognito Group

To test policies based on group membership, create a Cognito Group named AVPGroup within your User Pool, using the CreateGroup API with the USER_POOL_ID.

Terminal window
awslocal cognito-idp create-group \
--user-pool-id "$USER_POOL_ID" \
--group-name AVPGroup

The output will be:

Terminal window
{
"Group": {
"GroupName": "AVPGroup",
"UserPoolId": "us-east-1_1a971945fe4f47e7836833e5bef5da86",
"LastModifiedDate": "2025-05-06T14:33:50+05:30",
"CreationDate": "2025-05-06T14:33:50+05:30"
}
}

Step 5: Create a Cognito User and Get Tokens

Now, create a test user, set their password, add them to the AVPGroup, and initiate authentication to retrieve their ID and Access tokens.

5.1: Create the user

Terminal window
awslocal cognito-idp admin-create-user \
--user-pool-id "$USER_POOL_ID" \
--username avp-user \
--user-attributes Name=email,Value="avp@test.com" Name=email_verified,Value=true

5.2: Set the user’s password

Terminal window
awslocal cognito-idp admin-set-user-password \
--user-pool-id "$USER_POOL_ID" \
--username avp-user \
--password Test123! \
--permanent

5.3: Add the user to the group

Terminal window
awslocal cognito-idp admin-add-user-to-group \
--user-pool-id "$USER_POOL_ID" \
--username avp-user \
--group-name AVPGroup

5.4: Authenticate and get tokens

Terminal window
AUTH_OUTPUT=$(awslocal cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id "$CLIENT_ID" \
--auth-parameters USERNAME=avp-user,PASSWORD=Test123! \
--output json)
ID_TOKEN=$(echo $AUTH_OUTPUT | jq -r '.AuthenticationResult.IdToken')

The resulting IdToken is captured into the ID_TOKEN variable, ready for the authorization request.

Step 6: Create a Policy Store

A Policy Store is a container for your AVP schema and policies. You can create one using the CreatePolicyStore API.

Terminal window
POLICY_STORE_OUTPUT=$(awslocal verifiedpermissions create-policy-store \
--validation-settings mode=OFF \
--description "Policy Store with Cognito" \
--output json)
POLICY_STORE_ID=$(echo $POLICY_STORE_OUTPUT | jq -r '.policyStoreId')
echo "Policy Store ID: $POLICY_STORE_ID"

Step 7: Create an Identity Source

An Identity Source tells Verified Permissions how to interpret identity information from an external IdP, in this case, Cognito, using the CreateIdentitySource API.

First, generate a configuration file named identity_source.json using our previous variables:

Terminal window
cat <<EOF > identity_source.json
{
"cognitoUserPoolConfiguration": {
"userPoolArn": "$USER_POOL_ARN",
"clientIds":["$CLIENT_ID"],
"groupConfiguration": {"groupEntityType": "UserGroup"}
}
}
EOF
echo "Generated identity_source.json:"
cat identity_source.json

Now, create the Identity Source using this configuration:

Terminal window
awslocal verifiedpermissions create-identity-source \
--policy-store-id "$POLICY_STORE_ID" \
--principal-entity-type "User" \
--configuration file://identity_source.json

The output will be:

Terminal window
{
"createdDate": "2025-05-06T09:16:27.929347+00:00",
"identitySourceId": "8kvcln2xNr4YvRgVKhkm4c",
"lastUpdatedDate": "2025-05-06T09:16:27.929347+00:00",
"policyStoreId": "dfmlV05O8OKtRvs7UjwX6C"
}

This command configures AVP to recognize users from your Cognito pool and map Cognito groups to the UserGroup entity type within AVP.

Step 8: Create a Policy

Now, create a Cedar policy that grants permission based on the user’s group membership derived from the Cognito token.

This policy allows any principal who is a member of UserGroup::"AVPGroup" to perform the Action::"create" on the resource Album::"vacations".

Terminal window
cat <<EOF > policy_cognito.json
{
"static": {
"description": "Grant any User that is part of the UserGroup AVPGroup access to create the vacations Album",
"statement": "permit(principal in UserGroup::\"AVPGroup\", action == Action::\"create\", resource == Album::\"vacations\");"
}
}
EOF
echo "Generated policy_cognito.json:"
cat policy_cognito.json

Then, create the policy within your Policy Store. The policyId of the created policy is captured for reference.

Terminal window
POLICY_OUTPUT=$(awslocal verifiedpermissions create-policy \
--definition file://policy_cognito.json \
--policy-store-id "$POLICY_STORE_ID" \
--output json)
POLICY_ID=$(echo $POLICY_OUTPUT | jq -r '.policyId')
echo "Policy ID: $POLICY_ID"

Step 9: Authorize Request with Cognito Token

Finally, the setup can be tested by making an authorization request using the IsAuthorizedWithToken API. This API takes the Cognito IdToken directly.

Verified Permissions will use the Identity Source configuration to extract the principal’s identity and group membership (cognito:groups claim) from the token and evaluate it against the policy.

Terminal window
awslocal verifiedpermissions is-authorized-with-token \
--policy-store-id "$POLICY_STORE_ID" \
--action actionType=Action,actionId=create \
--resource entityType=Album,entityId=vacations \
--identity-token "$ID_TOKEN"

Since the user avp-user is part of the AVPGroup, and the policy allows members of this group to perform the requested action on the resource, the request should be allowed:

Terminal window
{
"decision": "ALLOW",
"determiningPolicies": [
{
"policyId": "e19OlOOv8zvyB3uWYpW3ZA"
}
],
"errors": [],
"principal": {
"entityType": "User",
"entityId": "us-east-1_1a971945fe4f47e7836833e5bef5da86|9fa13cb6-3238-44d8-a174-1a1970607d79"
}
}

The expected output showing an ALLOW decision confirms the successful integration.

Summary

In this tutorial, you successfully tested the integration between Amazon Verified Permissions and Cognito using LocalStack. Using LocalStack’s emulation, you can develop and test applications with Verified Permissions and Cognito locally, ensuring your authorization logic works correctly before deploying to production.

You can now explore advanced use cases or scenarios, such as:

  • Write Cedar policies that use additional attributes from Cognito ID or Access tokens
  • Test scenarios involving multiple groups or more fine-grained attribute-based access control (ABAC)
  • Incorporate integration testing into your CI/CD pipeline for automated validation

Harsh Mishra
Harsh Mishra
Engineer at LocalStack
Harsh Mishra is an Engineer at LocalStack and AWS Community Builder. Harsh has previously worked at HackerRank, Red Hat, and Quansight, and specialized in DevOps, Platform Engineering, and CI/CD pipelines.
Benjamin Simon
Benjamin Simon
Senior Software Engineer at LocalStack
Benjamin is a Senior Software Engineer at LocalStack. He is the service owner for S3, SNS and API Gateway services. Benjamin is interested in software architecture, building new features, and staying informed about emerging technologies.