Hello again in the series about building authorization with Amazon Verified Permissions (AVP) and Cedar. In the previous post, we focused on the AVP-CLI tool (which we will also use today). This time, we will focus on a new feature called batch authorization, which, in my opinion, is a game changer in terms of using AVP.
Batch Authorization
Batch authorization in AVP allows you to process up to 30 authorization decisions for a single principal or resource in a single API call. The entities of an API request can contain up to 100 principals and up to 100 resources.
One of the many objections to AVP was the theory that it is too expensive as a service. Now, where we can make many requests in one, it allows for a significant reduction in costs and an increase in performance, as everything goes in one request (and we will pay just for one request).
isAuthorized
vs batchIsAuthorized
Traditionally, the isAuthorized
action in AVP is used to process a single authorization request (If you use Test Bench in the AVP console, the AVP is using this API operation to make the authorization decision). This involves sending an individual request for each authorization decision, which can be resource-intensive, especially when multiple decisions are needed.
An example of an isAuthorized
request structure can be found below:
{
"policyStoreId": "your-policy-store-id",
"principal": {
"entityType": "Principal::Type",
"entityId": "entity_id"
},
"action": {
"actionType": "Action::Type",
"actionId": "action_id"
},
"resource": {
"entityType": "Resource::Type",
"entityId": "resource_id"
},
"entities": {
"entityList": []
},
"context": {
"contextMap": {}
}
}
This JSON file outlines the format for a typical single authorization request, including details about the principal, action, resource, entities, and context. More examples of authorization requests you can found in the avp-cli scenarios.
The response looks like this:
response {
decision: 'ALLOW',
determiningPolicies: [ { policyId: 'ID_OF_THE_POLICY' } ],
errors: []
}
It contains:
- decision - authz decision that indicated if the authorization request should be allowed or denied.
- determiningPolicies - The list of determining policies used to make the authorization decision.
- errors - Errors that occurred while making an authorization decision.
The batchIsAuthorized
action, however, allows for processing multiple authorization requests in a single API call. This is particularly useful when you need to make several authorization decisions simultaneously, either for one principal across multiple resources or for one resource across multiple principals. The structure of a batchIsAuthorized
request is as follows:
{
"policyStoreId": "your-policy-store-id",
"entities": {
"entityList": [
{
"identifier": {
"entityType": "Principal::Type",
"entityId": "entity_id"
},
"attributes": {},
"parents": []
},
{
"identifier": {
"entityType": "Resource::Type",
"entityId": "entity_id"
},
"attributes": {},
"parents": []
},
// Additional entities can be included here
]
},
"requests": [
{
"principal": {
"entityType": "Principal::Type",
"entityId": "entity_id"
},
"action": {
"actionType": "Action::Type",
"actionId": "action_id"
},
"resource": {
"entityType": "Resource::Type",
"entityId": "resource_id"
},
"context": {
"contextMap": {}
}
},
// Additional requests can be included here
]
}
In this format, the entities section defines a common list of entities (principals and resources) involved in the batch request. The requests array then specifies individual authorization requests, each comprising a principal, action, resource, and context.
The response looks like this:
"results": [
{
"decision": "ALLOW",
"determiningPolicies": [
{
"policyId": "policyId"
}
],
"errors": [],
"request": {
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "View"
},
"context": {
"contextMap": {}
},
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
}
}
},
{
"decision": "ALLOW",
"determiningPolicies": [
{
"policyId": "policyId"
}
],
"errors": [],
"request": {
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "GetDiscount"
},
"context": {
"contextMap": {}
},
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
}
}
},
...
As we can see, we have results
which is a series of Allow or Deny decisions for each request and the policies that produced them. We have an array of authorization decision objects, and with that, we have a request
key, which is the authorization request that initiated the decision.
E-commerce example
To demonstrate the power of batch authorization, I've added a new scenario in the AVP CLI tool - the Batch Scenario Scenario. This scenario simulates an e-commerce platform with various user roles, actions, and policies.
Schema Overview
Our schema includes entities like Product
, Role
, User
, and Order
, each with specific attributes. Actions such as View
, Edit
, Buy
, GetDiscount
, and Preorder
are defined to mimic typical e-commerce operations.
Policies
We've crafted policies that cater to different user roles and scenarios:
Admin Role - Order Editing Policy
permit(
principal in EcommerceStore::Role::"admin",
action in [EcommerceStore::Action::"Edit"],
resource
)
when { resource.status == "paid" };
This policy allows users with the admin role to edit orders. However, it restricts editing based on the order's status. Admins can only edit orders that are in the "paid" status, ensuring that orders in other stages of processing are not altered.
Customer Role - Basic Access Policy
permit(
principal in EcommerceStore::Role::"customer",
action in [EcommerceStore::Action::"Buy", EcommerceStore::Action::"View"],
resource
);
Customers are granted the ability to perform basic actions like Buy
and View
on products. This policy is straightforward and applies to all customers, allowing them to browse and purchase products from the e-commerce platform.
Premium Membership - Special Privileges Policy
permit (
principal,
action in
[EcommerceStore::Action::"GetDiscount",
EcommerceStore::Action::"Preorder"],
resource
)
when { principal has premiumMembership && principal.premiumMembership == true };
This policy is designed for users with a premium membership. It allows them to access special actions like GetDiscount
and Preorder
on products. The policy checks if the user has a premiumMembership
attribute set to true
, indicating their premium status.
Testing Policies
I've designed two types of tests for this scenario: single authorization requests and batch authorization requests, each serving a unique purpose in our testing strategy.
Single Authorization Request Tests
These tests focus on individual authorization requests, assessing one action at a time. They help us verify the specific behavior of our policies in granular scenarios. Examples include:
Admin Editing Paid Orders: Checks if an admin user (Daniel) can edit orders with the status "paid."
Customer Viewing Products: Verifies that a customer (Tom) can view any product.
Premium Member Discounts: Ensures a premium member (Ken) can access discounts on products.
Batch Authorization Tests
Batch tests are more comprehensive, allowing us to evaluate multiple authorization requests in a single API call. We've prepared two types of batch tests:
Resource-Oriented Batch Test: This test checks multiple resources against a single principal to determine what actions they can perform. For instance, it can assess which orders an admin user is authorized to edit. This approach is particularly useful for scenarios where a user's role or status might grant them varying levels of access across different resources.
Principal-Oriented Batch Test: Conversely, this test evaluates multiple actions for a single principal on a specific resource. It helps us understand the range of actions a user (like Ken) can perform on a particular product. This type of test is essential for applications where user permissions vary based on the resource they are interacting with.
To test the BatchIsAuthorized
, we cannot do this through the Test Bench, where we can test our isAuthorized
requests to AVP. To solve this problem, I added the option to test batchIsAuthorized
to avp-cli. You need to select it from the manual approach option, select make a batch authorization decision, and provide a path to the json test file (use structureBatchAuthorizationRequest.json
as a reference) It looks like this:
? What would you like to do? Manual approach
? What would you like to do? Make a batch authorization decision
? Enter the path for batch authorization json test file structureBatchAuthorizationRequest.json
Making batch authorization decision...
┌──────────┬──────────────────────────────┬────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────
│ Decision │ Determining Policies │ Errors │ Policy Store ID │ Principal │ Action │ Resource
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────
│ ALLOW │ Vz4ZUYv36HwY6v65XTix4i │ │ JahHZkVn82ryyn5qMfBXG9 │ EcommerceStore::User::Ken │ EcommerceStore::Action::View │ EcommerceStore::Product::Hat
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────
│ ALLOW │ 4ZbngosNQ8ZCbaBcEQ4SE9 │ │ JahHZkVn82ryyn5qMfBXG9 │ EcommerceStore::User::Ken │ EcommerceStore::Action::GetD │ EcommerceStore::Product::Hat
│ │ │ │ │ │ iscount │
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────
│ ALLOW │ Vz4ZUYv36HwY6v65XTix4i │ │ JahHZkVn82ryyn5qMfBXG9 │ EcommerceStore::User::Ken │ EcommerceStore::Action::Buy │ EcommerceStore::Product::Hat
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────
│ ALLOW │ 4ZbngosNQ8ZCbaBcEQ4SE9 │ │ JahHZkVn82ryyn5qMfBXG9 │ EcommerceStore::User::Ken │ EcommerceStore::Action::Preo │ EcommerceStore::Product::Hat
│ │ │ │ │ │ rder │
├──────────┼──────────────────────────────┼────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────
│ DENY │ │ │ JahHZkVn82ryyn5qMfBXG9 │ EcommerceStore::User::Ken │ EcommerceStore::Action::Edit │ EcommerceStore::Product::Hat
└──────────┴──────────────────────────────┴────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────
For our Batch Scenario, we have also the option to test prepared batch authorization test requests like this:
? What would you like to do? Test Batch Authorization Scenario
? Choose a scenario ecommerceBatchScenario
? Choose a test (Use arrow keys)
❯ Checking multiple actions for a single principal (For specific product, What action can be taken by Ken
Checking multiple resources, to check which orders can admin edit.
...
Remember to update policy_store_id
in here and here.
Example test structure:
{
"policyStoreId": "your-policy-store-id",
"entities": {
"entityList": [
{
"identifier": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"attributes": {
"premiumMembership": {
"boolean": true
}
},
"parents": [
{
"entityType": "EcommerceStore::Role",
"entityId": "customer"
}
]
},
{
"identifier": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
},
"attributes": {},
"parents": []
}
]
},
"requests": [
{
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "View"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
},
"context": {
"contextMap": {}
}
},
{
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "GetDiscount"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
},
"context": {
"contextMap": {}
}
},
{
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "Buy"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
},
"context": {
"contextMap": {}
}
},
{
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "Preorder"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
},
"context": {
"contextMap": {}
}
},
{
"principal": {
"entityType": "EcommerceStore::User",
"entityId": "Ken"
},
"action": {
"actionType": "EcommerceStore::Action",
"actionId": "Edit"
},
"resource": {
"entityType": "EcommerceStore::Product",
"entityId": "Hat"
},
"context": {
"contextMap": {}
}
}
]
}
Conclusion
- Batch authorization in AVP offers a powerful and efficient way to handle multiple authorization requests simultaneously (up to 30 requests principal or request oriented, with up to 100 entities (principals and resources)).
- It will decrease costs for requests to AVP as well as improve performance.
- Keep in mind that you cannot use the
BatchIsAuthorized
with thetoken
option, it's not supported currently. - Keep in mind that you cannot use the
BatchIsAuthorized
within the Test Bench currently.
Top comments (0)