DEV Community

Cover image for Authorization and Amazon Verified Permissions - A New Way to Manage Permissions Part VIII: Integration with Cognito
Daniel Aniszkiewicz for AWS Community Builders

Posted on • Updated on

Authorization and Amazon Verified Permissions - A New Way to Manage Permissions Part VIII: Integration with Cognito

Welcome back to my last blogpost of series on AWS Verified Permissions (AVP)! In the previous blogpost, we discussed the importance of auditing and pricing in an enterprise-grade authorization system. Today, we're going to explore how to integrate AVP with AWS Cognito, a powerful identity service that can help you manage user identities and authentication in your applications.

Integrating AVP with AWS Cognito

In any application, managing user identities and controlling their access to resources is a crucial aspect. Amazon Cognito is a service that helps you manage your users and their authentication. When integrated with AVP, Amazon Cognito can provide the identity context for your authorization policies. This means that you can use information from a user's Cognito identity, such as their user attributes, to make fine-grained access control decisions in AVP.

However, it's important to note that AVP doesn't currently process the cognito:groups claim, which means that Cognito integration can be used primarily for ABAC (Attribute-Based Access Control) rather than RBAC (Role-Based Access Control). The team is working on enabling support for group claims, which would allow for RBAC-type policies.

Use Case: E-commerce Application

Let's consider an e-commerce application where we have Sellers. Each seller has a custom attribute called discountPrivilege which is a string indicating whether the seller has the privilege to give discounts or not.

Creating the Schema

In AVP, we define the structure of the entities, actions, and context that the policy will use in a schema. For our use case, our schema will include a "Seller" entity type with a "custom:discountPrivilege" attribute, and a "Product" entity type. Our actions will include "Read" and "Discount".

Here's how our schema looks:

{
    "MyEcommerceApp": {
        "entityTypes": {
            "Product": {
                "shape": {
                    "type": "Record",
                    "attributes": {}
                }
            },
            "Seller": {
                "shape": {
                    "type": "Record",
                    "attributes": {
                        "custom": {
                            "attributes": {
                                "discountPrivilege": {
                                    "required": true,
                                    "type": "String"
                                }
                            },
                            "required": true,
                            "type": "Record"
                        }
                    }
                }
            }
        },
        "actions": {
            "Discount": {
                "appliesTo": {
                    "resourceTypes": [
                        "Product"
                    ],
                    "principalTypes": [
                        "Seller"
                    ],
                    "context": {
                        "type": "SellerContext"
                    }
                }
            },
            "Read": {
                "appliesTo": {
                    "resourceTypes": [
                        "Product"
                    ],
                    "context": {
                        "type": "SellerContext"
                    },
                    "principalTypes": [
                        "Seller"
                    ]
                }
            }
        },
        "commonTypes": {
            "SellerContext": {
                "attributes": {
                    "token": {
                        "attributes": {
                            "client_id": {
                                "type": "String"
                            }
                        },
                        "type": "Record"
                    }
                },
                "type": "Record"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Setting Up the Identity Source with Cognito

Before we can create our policy, we need to set up an identity source in AVP. The identity source is where AVP gets the identity context for authorization decisions. In our case, we're using Cognito as our identity source.

Here are the steps to set up a Cognito identity source in AVP:

  • In the AVP console, navigate to "Identity sources" and click "Create identity source".
  • For "Cognito user pool details" please select AWS Region and Cognito User Pool.
  • For "Principal details" for "Principal type" select "MyEcommerceApp::Seller". For "App client", enter the ID of the app client in your user pool that will be making requests to AVP. For "Client application validation", select "Only accept tokens with matching client application IDs". This ensures that AVP only accepts tokens that were issued for the specified app client. Provide the "Client Application Id" (It's located in Cognito, we will cover it in the Testing section) Click "Create identity source".

Identity source

Now it should looks like this:

Created identity source

Creating the Policy

Next, we define the rules for access control in a policy. Our policy allows a seller to apply discounts to a product when the seller's "custom:discountPrivilege" attribute is set to "agreed".

Here's how our policy looks:

permit (
    principal,
    action in [MyEcommerceApp::Action::"Discount"],
    resource
)
when { principal.custom.discountPrivilege == "agreed" };
Enter fullscreen mode Exit fullscreen mode

How It All Works Together

With our schema, identity source, and policy set up, here's how the authorization process works:

  • A Seller logs in through Cognito and receives an ID token. This token includes the seller's user attributes, including the custom:discountPrivilege attribute.
  • The seller makes a request to our application to perform an action, such as applying a discount to a product. The request includes the seller's ID token.
  • Our application sends the ID token to AVP along with the requested action and resource. This is done through the IsAuthorizedWithToken API operation.
  • AVP validates the ID token and extracts the identity context, which includes the custom:discountPrivilege attribute.
  • AVP evaluates the policy using the identity context, action, and resource. If the policy permits the action (i.e., if the custom:discountPrivilege attribute is set to "agreed"), AVP returns an "ALLOW" decision. Otherwise, it returns a "DENY" decision.
  • Our application uses the decision from AVP to either allow or deny the seller's request.

This process allows us to make fine-grained access control decisions based on user attributes from Cognito. It's a powerful way to implement attribute-based access control (ABAC) in our application.

In the next section, we'll walk through how to test this setup to ensure everything is working as expected.


Testing the Setup

This time we cannot use the Test Bench from AVP, we need to do it differently. We first need to create a Cognito user pool and an app client.
Here's a quick guide on how to do this:

  1. Create a Cognito User Pool: In the AWS Management Console, navigate to the Cognito service and click on "Manage User Pools". Click "Create a user pool", give it a name, and click "Review defaults". On the next page, click "Create pool".

User pool creation

  1. Set Password Policy: Make it default.

  2. Disable MFA: In the "MFA and verifications" settings, set "Which second factors do you want to enable?" to "None". This is for simplicity in our testing setup; in a production environment, you would typically want to enable MFA for added security.

  3. Add Custom Attribute: In the "Attributes" settings, click on "Add custom attribute". Set the attribute name to "discountPrivilege", the attribute type to "String", and check the "Mutable" box. This allows the attribute to be changed after the user is created.

custom attribute

  1. Create an App Client: In the "App clients" settings, click "Add an app client". Give it a name, uncheck all the "Auth Flows Configuration" options except for ALLOW_ADMIN_USER_PASSWORD_AUTH and ALLOW_USER_PASSWORD_AUTH, and click "Create app client".

App client

Now that we have our Cognito setup, we can create a user and assign them the discountPrivilege attribute. Here's how:

  1. In the Cognito user pool, navigate to "Users and groups" and click "Create user". Fill in the required fields, set "Email verified" to "True", and in the "Custom attributes" section, set discountPrivilege to "agreed".

  2. Note down the "App client id" from the "App client settings" in your user pool. We'll need this for testing (user pool as well).

Testing flow with AWS CLI

With our Cognito setup ready, we can now test our AVP integration. Here's how:

Before we can test our setup, we need to authenticate our user and obtain an ID token (Not the access token, as there will be not custom claims included). When a user is created in Cognito, they are required to change their password after their first sign-in. This process involves initiating an authentication flow, responding to the new password challenge, and then authenticating again with the new password to get the ID token.

The command to do it is like follow:

aws cognito-idp admin-respond-to-auth-challenge \
    --user-pool-id user-pool-id \
    --client-id client-id \
    --challenge-name NEW_PASSWORD_REQUIRED \
    --challenge-responses USERNAME=username,NEW_PASSWORD='YourNewPassword' \
    --session "YourSessionString"
Enter fullscreen mode Exit fullscreen mode

Here's how to do this with the AWS CLI:

Initiate the Authentication Flow: Use the admin-initiate-auth command to initiate the authentication flow. This command requires the user pool ID, the app client ID, and the user's username and password.

aws cognito-idp admin-initiate-auth \
    --user-pool-id <user_pool_id> \
    --client-id <client_id> \
    --auth-flow ADMIN_USER_PASSWORD_AUTH \
    --auth-parameters USERNAME=<username>,PASSWORD=<password>
Enter fullscreen mode Exit fullscreen mode

Deal with New Password Challenge, and auth again. Then we should obtain the ID token, we will use it to test our AVP policy.

If you decode it with jwt.io it should looks like this:

"email_verified": true,
  "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-user-pool",
  "cognito:username": "92d514d4-0071-70fa-2ab7-201b7f22167a",
  "custom:discountPrivilege": "agreed",
  "origin_jti": "e166c8de-3798-49be-9e39-0ddfe3055cda",
  "aud": "4bagaej4rb8m1t028lv5vsio8o",
  "event_id": "519e9b73-2231-4afc-b881-f748b9d6000e",
  "token_use": "id",
  "auth_time": 1691072849,
  "exp": 1691076449,
  "iat": 1691072849,
  "jti": "f33a5626-5f5d-45ce-8848-61a115bbdce7",
  "email": "example.com"
}
Enter fullscreen mode Exit fullscreen mode

Testing the AVP Policy

Now that we have our ID token, we can use the is-authorized-with-token command to test our AVP policy (make sure you have updated AWS CLI). This command requires the policy store ID, the ID token, the action, the resource, and the context.

aws verifiedpermissions is-authorized-with-token \
    --policy-store-id <policy_store_id> \
    --identity-token <id_token> \
    --action actionType="MyEcommerceApp::Action",actionId="Discount" \
    --resource entityType="MyEcommerceApp::Resource",entityId="Product" \
    --context '{"contextMap": {"custom.discountPrivilege": {"string": "agreed"}}}' \
    --region <region>
Enter fullscreen mode Exit fullscreen mode

This command will return a decision ("ALLOW" or "DENY") and the policies that determined the decision.

{
    "decision": "ALLOW",
    "determiningPolicies": [
        {
            "policyId": "XBN1W6aDjwYdDC1HQyVGXX"
        }
    ],
    "errors": []
}
Enter fullscreen mode Exit fullscreen mode

And that's it! You've successfully integrated AVP with AWS Cognito and tested your setup using the AWS CLI.

Using avp-cli for the Scenario

The scenario we've discussed in this post is included in the avp-cli tool. This command-line interface tool simplifies the process of creating and managing schemas, policies, and policy stores in AVP. However, to use the tool for this scenario, you need to have your Cognito setup ready as per the instructions provided in this post.

In the library there is also support for isAuthorizedWithToken so we can easily make an authorization request.

Conclusion

In the next blogpost we will focus on hierarchies. I'm also working on enhancing the avp-cli to add more functionalities to it, so stay tuned for updates!

Top comments (5)

Collapse
 
pigius profile image
Daniel Aniszkiewicz

Hi @ukiyo unfortunately you need to create a new user pool.

To change the name of a user pool, create a new user pool.

Based on the documentation.

Collapse
 
jaison profile image
jaison

while following these steps occuring an error in policy creation.
error message: Attribute not found in record or entity custom

Collapse
 
pigius profile image
Daniel Aniszkiewicz

Hi, I will try to find time to take a look in next days to check it :)

Collapse
 
pigius profile image
Daniel Aniszkiewicz

Hi @jaison can you please check again? I've changed the schema and checked it again, works fine for me. If you still have an issue, please contact me directly.

Collapse
 
ukiyo profile image
Sunandini • Edited

Hey Daniel
I just wanted to ask this, is it possible to change the user pool name, I have check on this , the only option is to create new pool ,if it is correct then why is that so??