Authorization and Amazon Verified Permissions - A New Way to Manage Permissions Part XI: Batch Authorization

pigius

Daniel Aniszkiewicz

Posted on December 6, 2023

Authorization and Amazon Verified Permissions - A New Way to Manage Permissions Part XI: Batch Authorization

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": {}
    }
}
Enter fullscreen mode Exit fullscreen mode

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: []
}
Enter fullscreen mode Exit fullscreen mode

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
   ]
}
Enter fullscreen mode Exit fullscreen mode

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"
        }
      }
    },
...
Enter fullscreen mode Exit fullscreen mode

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

Schema

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.
Entity types

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" };
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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 
└──────────┴──────────────────────────────┴────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────
Enter fullscreen mode Exit fullscreen mode

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.
...
Enter fullscreen mode Exit fullscreen mode

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": {}
         }
      }
   ]
}
Enter fullscreen mode Exit fullscreen mode

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 the token option, it's not supported currently.
  • Keep in mind that you cannot use the BatchIsAuthorized within the Test Bench currently.
💖 💪 🙅 🚩
pigius
Daniel Aniszkiewicz

Posted on December 6, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related