AWS Security Blog

Authorize API Gateway APIs using Amazon Verified Permissions with Amazon Cognito or bring your own identity provider

August 9, 2024: This post has been updated to reflect a new feature in Amazon Verified Permissions that supports OpenID Connect (OIDC) compliant identity providers as identity source


Externalizing authorization logic for application APIs can yield multiple benefits for Amazon Web Services (AWS) customers. These benefits can include freeing up development teams to focus on application logic, simplifying application and resource access audits, and improving application security by using continual authorization. Amazon Verified Permissions is a scalable permissions management and fine-grained authorization service that you can use to externalize application authorization. Along with controlling access to application resources, you can use Verified Permissions to restrict API access to authorized users by using Cedar policies. However, a key challenge in adopting an external authorization system like Verified Permissions is the effort involved in defining the policy logic and integrating with your API. This blog post shows how Verified Permissions accelerates the process of securing REST APIs that are hosted on Amazon API Gateway for customers using Amazon Cognito or an OpenID Connect (OIDC) compliant identity provider (IdP).

Setting up API authorization using Amazon Verified Permissions

As a developer, there are several tasks you need to do in order to use Verified Permissions to store and evaluate policies that define which APIs a user is permitted to access. Although Verified Permissions enables you to decouple authorization logic from your application code, you might need to spend time learning the Cedar policy language, defining a policy schema, authoring policies, and integrating Verified Permissions into your applications. Lastly, you might need to spend additional time developing and testing the AWS Lambda authorizer function logic that builds the authorization request for Verified Permissions and enforces the authorization decision. This can be a daunting task for a developer who is new to the service.

Getting started with the simplified wizard

Amazon Verified Permissions now includes a console-based wizard that you can use to quickly create building blocks to set up your application’s API Gateway to use Verified Permissions for authorization. Verified Permissions generates an authorization model based on your APIs and policies that allows only authorized user groups access to your APIs. Additionally, it deploys a Lambda authorizer, which you attach to the APIs you want to secure. After the authorizer is attached, API requests are authorized by Verified Permissions. The generated Cedar policies and schema flatten the learning curve, yet allow you full control to modify and help you adhere to your security requirements.

Overview of the sample application

In this blog post, we demonstrate how you can simplify the task of securing permissions to a sample application API by using the Verified Permissions console-based wizard. We use a sample pet store application which has two resources:

  1. PetStore – An Amazon API Gateway REST API derived from importing the PetStore example API and extending it with a mock integration for administration. This mock integration returns a message with a URI path that uses {"statusCode": 200} as the integration request and {"Message": "User authorized for $context.path"} as the integration response.
  2. User directory – An identity source that can be used to define users and their properties when user requests access to application resources. We will use an Amazon Cognito user pool called PetStorePool with users in one of three groups: customers, employees, and owners. Alternatively, you can bring your own OIDC compliant IdP with the above three user groups.

The PetStore has the following four authorization requirements that allow access to the related resources. All other behaviors should be denied.

  1. Both authenticated and unauthenticated users are allowed to access the root URL.
    • GET /
  2. All authenticated users are allowed to get the list of pets, or get a pet by its identifier.
    • GET /pets
    • GET /pets/{petid}
  3. The employees and owners group are allowed to add new pets.
    • POST /pets
  4. Only the owners group is allowed to perform administration functions. These are defined using an API Gateway proxy resource that enables a single integration to implement a set of API resources.
    • ANY /admin/{proxy+}

Walkthrough

Verified Permissions includes a setup wizard that connects an Amazon Cognito user pool or an OIDC IdP to an API Gateway REST API and secures resources based on group memberships. In this section, we provide a walkthrough of the wizard that generates the authorization building blocks for our sample application.

To set up API authorization based on user groups

  1. On the Amazon Verified Permissions page in the AWS Management Console, choose Create new policy store.
  2. On the Specify policy store details page, under Starting options, select Set up with API Gateway and an identity provider, and then choose Next.

    Figure 1: Starting options

    Figure 1: Starting options

Import API resources and actions

  1. On the Import resources and actions page under API Gateway details, select the API and Deployment stage from the dropdown lists. (A REST API stage is a named reference to a deployment.) For this example, we selected the PetStore API and the demo stage.

    Figure 2: API Gateway and deployment stage

    Figure 2: API Gateway and deployment stage

  2. Choose Import API to generate a Map of imported resources and actions. For our example, this list includes Action::"get /pets" for getting the list of pets, Action::"get /pets/{petId}" for getting a single pet, and Action::"post /pets" for adding a new pet. Choose Next.

    Figure 3: Map of imported resources and actions

    Figure 3: Map of imported resources and actions

Choose identity source

In this step, you configure the identity source used by your application to authenticate and manage users. You can connect to your existing Amazon Cognito user pool or add an external OIDC IdP. When using an existing Cognito user pool, Verified Permissions automatically retrieves your user pool configuration and allows you to assign permissions based on Cognito groups. When using an external OIDC IdP, you need to provide the OIDC issuer URL and manually specify group configurations. The OIDC issuer URL is used by Verified Permission to verify the token signature and expiration.

Option 1: Use an existing Amazon Cognito user pool

  1. On the Choose identity source page, under Configure provider section, select Amazon Cognito.

    Figure 4: Choose Cognito user pool as identity provider

    Figure 4: Choose Cognito user pool as identity provider

  2. Under Identity source section, select a Cognito user pool (PetStorePool in our example). For Token type to pass to API, select a token type. For our example, we chose the default value, Access token, because Cognito recommends using the access token to authorize API operations. The additional claims available in an id token may support more fine-grained access control. For Client application validation, we also specified the default, to not validate that tokens match a configured app client ID. Consider validation when you have multiple user pool app clients configured with different permissions.

    Figure 5: Choose Cognito user pool PetStorePool as identity source

    Figure 5: Choose Cognito user pool PetStorePool as identity source

  3. Choose Next.
  4. On the Assign actions to groups page under Group selection, choose the Cognito user pool groups that can take actions in the application. This solution uses native Cognito group memberships to control permissions. In Figure 6, because the customers group is not used for access control, we deselected it and it isn’t included in the generated policies. Instead, access to get /pets and get/pets/{petId} is granted to all authenticated users using a different authorizer that we define later in this post.

    Figure 6: Assign actions to groups

    Figure 6: Assign actions to groups

  5. For each of the groups, choose which actions are allowed. In our example, post /pets is the only action selected for the employees group. For the owners group, all of the /admin/{proxy+} actions are additionally selected. Choose Next.

    Figure 7: Cognito groups employees and owners

    Figure 7: Cognito groups employees and owners

Option 2: Use an external OIDC IdP

  1. On the Choose identity source page, select External OIDC Provider.

    Figure 8: Choose External OIDC provider as identity source

    Figure 8: Choose External OIDC provider as identity source

  2. For OIDC provider details, enter the Issuer URL. Please note that your issuer URL must host an OIDC discovery document at /.well-known/openid-configuration. For our example, we used an open-sourced Keycloak container instance. You can review and download the Keycloak IdP OIDC discovery document to find the issuer URL. In our example, the issuer URL is https://f28rmekmvd.us-east-1.awsapprunner.com/auth/realms/Demo.

    Figure 9: Enter external OIDC provider details

    Figure 9: Enter external OIDC provider details

  3. For Token type, choose the type of JWT you want your application to submit for authorization. We suggest to use Access Token as token claims will be mapped to context as context.token.attribute_name in the authorization request to support role-based access control (RBAC) models. You can choose to use ID Token if your application authorizes user based on user’s identity attributes. You can read more about working with identity sources and mapping token attributes in schema and policies.
  4. For User and group token claims, enter the claim represented by your IdP. The Verified Permissions wizard creates policies based on this claim mapping, and your policy store authorizes requests based on this claim in the tokens. If you’re unsure about how your external IdP represents user and group claims in the token, decode a JWT and look into the payload. In our example, the user claim is mapped to sub, and the group claim is mapped to groups.
    {
      "exp": 1720450500,
      "iat": 1720450200,
      "auth_time": 1720450200,
      "jti": "4bd89020-0f93-48c7-8ad0-8ba4a6068e65",
      "iss": "https://f28rmekmvd.us-east-1.awsapprunner.com/auth/realms/Demo",
      "aud": "account",
      "sub": "86822a41-afc0-4478-b7bb-79432c567c91",
      "typ": "Bearer",
      "azp": "demo",
      "scope": "openid email profile",
      "groups": [
        "/employees",
        "/owners"
      ],
      "preferred_username": "testuser"
    }

    Figure 10: Enter external OIDC provider token claims detail

    Figure 10: Enter external OIDC provider token claims detail

  5. Choose Next.
  6. On the Assign actions to groups page, enter the group name and select the action allowed. In our example, we created the employees’ group and allowed post /pets action.
  7. Choose Add more groups to create another group for owners, and additionally allow all the /admin/{proxy+} actions. Choose Next.

    Figure 11: OIDC IdP groups employees and owners

    Figure 11: OIDC IdP groups employees and owners

Deploy app integration

  1. On the Deploy app integration page, review the API Gateway Integration details, and choose Now under Start authorizing for API section. Choose Create policy store.

    Figure 12: Enable API Gateway integration

    Figure 12: Enable API Gateway integration

  2. On the Create policy store summary page, review the progress of the setup. Choose Check deployment to check the progress of Lambda authorizer.

    Figure 13: Create policy store

    Figure 13: Create policy store

The setup wizard deploys a CloudFormation stack with a Lambda authorizer. This authorizes access to the API Gateway resources for the employees and owners groups.

For the second use case where resources should be authorized for all authenticated users, a separate Cognito user pool authorizer is required to perform authorization. You can use the following AWS CLI apigateway create-authorizer command to create the authorizer.

aws apigateway create-authorizer \
--rest-api-id wrma51eup0 \
--name "Cognito-PetStorePool" \
--type COGNITO_USER_POOLS \
--identity-source "method.request.header.Authorization" \
--provider-arns "arn:aws:cognito-idp:us-west-2:000000000000:userpool/us-west-2_iwWG5nyux"

If you’re using your own OIDC IdP, you can modify your policy to grant access to all user groups and skip the preceding steps to configure a Cognito authorizer.

permit(
  principal,
  action in [PetStore::Action::"get /pets",PetStore::Action::"get /pets/{petId}"],
  resource
);

After the CloudFormation stack deployment completes and the second Cognito authorizer is created, there are two authorizers that can be attached to PetStore API resources, as shown in Figure 14.

Figure 14: PetStore API Authorizers

Figure 14: PetStore API Authorizers

In Figure 14, Cognito-PetStorePool is a Cognito user pool authorizer. Because this example uses an access token, an authorization scope (for example, a custom scope like petstore/api) is specified when attached to the GET /pets and GET /pets/{petId} resources.

AVPAuthorizer-XXX is a request parameter-based Lambda authorizer, which determines the caller’s identity from the configured identity sources. In Figure 14, these sources are Authorization (Header), httpMethod (Context), and path (Context). This authorizer is attached to the POST /pets and ANY /admin/{proxy+} resources.

This combination of multiple authorizers and caching reduces the number of authorization requests to Verified Permissions. For API calls that are available to all authenticated users, using the Cognito-PetStorePool authorizer instead of a policy permitting the customers group helps avoid chargeable authorization requests to Verified Permissions.

Authorization caching is initially set at 120 seconds and can be configured using the API Gateway console. Applications where the users initiate the same action multiple times or have a predictable sequence of actions will experience high cache hit rates. For repeated API calls that use the same token, AVPAuthorizer-XXX caching results in lower latency, fewer requests per second, and reduced costs from chargeable requests. Please note that use of caching can delay the time between policy updates and policy enforcement, meaning that the policy updates to Verified Permissions are not realized until the timeout or the FlushStageAuthorizersCache API is called.

Deployment architecture

Figure 15 illustrates the runtime architecture after you have used the Verified Permissions setup wizard to perform the deployment and configuration steps. After the users are authenticated with the Cognito PetStorePool or the external OIDC IdP, API calls to the PetStore API are authorized with the access token. Fine-grained authorization is performed by Verified Permissions using a Lambda authorizer. The wizard automatically creates the following four items for you, which are labelled in Figure 15:

  1. A Verified Permissions policy store that is connected to an identity source (Cognito user pool or external OIDC IdP).
  2. A Cedar schema that defines the User and UserGroup entities, and an action for each API Gateway resource.
  3. Cedar policies that assign permissions for the employees and owners groups to related actions.
  4. Lambda authorizer that is configured on the API Gateway.

    Figure 15: Architecture diagram after deployment

    Figure 15: Architecture diagram after deployment

Verified Permissions uses the Cedar policy language to define fine-grained permissions. The default decision for an authorization response is "deny". The Cedar policies that are generated by the setup wizard can determine an "allow" decision. The principal for each policy is a UserGroup entity with an entity ID format of {user pool id}|{group name} when using Cognito as the identity source, or entity ID format of {OIDC Issuer URL}|{group name} when using an OIDC IdP. The action IDs for each policy represent the set of selected API Gateway HTTP methods and resource paths. Note that post /pets is permitted for both employees and owners. The resource in the policy scope is unspecified, because the resource is implicitly the application.

permit (
    principal in PetStore::UserGroup::"us-west-2_iwWG5nyux|employees",
    action in [PetStore::Action::"post /pets"],
    resource
);

permit (
    principal in PetStore::UserGroup::"us-west-2_iwWG5nyux|owners",
    action in
        [PetStore::Action::"delete /admin/{proxy+}",
         PetStore::Action::"post /admin/{proxy+}",
         PetStore::Action::"get /admin/{proxy+}",
         PetStore::Action::"patch /admin/{proxy+}",
         PetStore::Action::"put /admin/{proxy+}",
         PetStore::Action::"post /pets"],
    resource
);

Validating API security

You can validate your policies and API access for both authorized and unauthorized users by using different access tokens with terminal-based cURL commands. For readability, a set of environment variables is used to represent the actual values. TOKEN_C, TOKEN_E, and TOKEN_O contain valid access tokens for respective users in the customers, employees, and owners groups. API_STAGE is the base URL for the PetStore API and demo stage that we selected earlier.

To test that an unauthenticated user is allowed for the GET / root path (Requirement 1 as described in the Overview section of this post), but not allowed to call the GET /pets API (Requirement 2), run the following curl commands. The Cognito-PetStorePool authorizer should return {"message":"Unauthorized"}.

curl -X GET ${API_STAGE}/
<html>
...Welcome to your Pet Store API...
</html>

curl -X GET ${API_STAGE}/pets
{"message":"Unauthorized"}

To test that an authenticated user is allowed to call the GET /pets API (Requirement 2) by using an access token (due to the Cognito-PetStorePool authorizer), run the following cURL commands. The user should receive an error message when they try to call the POST /pets API (Requirement 3), because of the AVPAuthorizer. There are no Cedar policies defined for the customers group with the action post /pets.

curl -H "Authorization: Bearer ${TOKEN_C}" -X GET ${API_STAGE}/pets
[
  {
    "id": 1,
    "type": "dog",
    "price": 249.99
  },
  {
    "id": 2,
    "type": "cat",
    "price": 124.99
  },
  {
    "id": 3,
    "type": "fish",
    "price": 0.99
  }
]

curl -H "Authorization: Bearer ${TOKEN_C}" -X POST ${API_STAGE}/pets
{"Message":"User is not authorized to access this resource with an explicit deny"}

The following commands will verify that a user in the employees group is allowed the post /pets action (Requirement 3).

curl -H "Authorization: Bearer ${TOKEN_E}" \
     -H "Content-Type: application/json" \
     -d '{"type": "dog","price": 249.99}' \
     -X POST ${API_STAGE}/pets
{
  "pet": {
    "type": "dog",
    "price": 249.99
  },
  "message": "success"
}

The following commands will verify that a user in the employees group is not authorized for the admin APIs, but a user in the owners group is allowed (Requirement 4).

curl -H "Authorization: Bearer ${TOKEN_E}" -X GET ${API_STAGE}/admin/curltest1
{"Message":"User is not authorized to access this resource with an explicit deny"} 

curl -H "Authorization: Bearer ${TOKEN_O}" -X GET ${API_STAGE}/admin/curltest1
{"Message": "User authorized for /demo/admin/curltest1"}

Try it yourself

How could this work with your user pool and REST API? Before you try out the solution, make sure that you have the following prerequisites in place, which are required by the Verified Permissions setup wizard:

  1. A Cognito user pool or bring your own OIDC compliant IdP, along with user groups that control authorization to the API endpoints.
  2. An API Gateway REST API in the AWS Region where you intend to create the Verified Permission policy store, as well as in the same Region as the Cognito user pool.

As you review the resources generated by the solution, consider these authorization modeling topics:

  • Are access tokens or ID tokens preferable for your API? Are there custom claims on your tokens that you would use in future Cedar policies for fine-grained authorization?
  • Do multiple authorizers fit your model, or do you have an “all users” group for use in Cedar policies?
  • How might you extend the Cedar schema, allowing for new Cedar policies that include URL path parameters, such as {petId} from the example?

Conclusion

This post demonstrated how the Amazon Verified Permissions setup wizard provides you with a step-by-step process to build authorization logic for API Gateway REST APIs using a Cognito user pool or your OIDC identity provider. The wizard generates a policy store, schema, and Cedar policies to manage access to API endpoints based on the specification of the APIs deployed. In addition, the wizard creates a Lambda authorizer that authorizes access to the API Gateway resources based on the configured user groups. This removes the modeling effort required for initial configuration of API authorization logic and setup of Verified Permissions to receive permission requests. You can use the wizard to set up and test access controls to your APIs based on user groups in non-production accounts. You can further extend the policy schema and policies to accommodate fine-grained or attribute-based access controls, based on specific requirements of the application, without making code changes.

Learn more about how Verified Permissions provides fine-grained authorization with leading identity providers, such as CyberArk, Okta, Ping Identity, Transmit Security, visit Amazon Verified Permissions Partners.

If you have feedback about this post, submit comments in the Comments section below. If you have questions about this post, start a new thread on the Amazon Verified Permissions re:Post or contact AWS Support.

Kevin Hakanson

Kevin Hakanson
Kevin is a Senior Solutions Architect for AWS World Wide Public Sector, based in Minnesota. He works with EdTech and GovTech customers to ideate, design, validate, and launch products using cloud-native technologies and modern development practices. When not staring at a computer screen, he is probably staring at another screen, either watching TV or playing video games with his family.

Sowjanya Rajavaram

Sowjanya Rajavaram
Sowjanya is a Senior Solutions Architect who specializes in identity and security solutions at AWS. Her career has been focused on helping customers of all sizes solve their identity and access management problems. She enjoys traveling and experiencing new cultures and food.

Edward Sun

Edward Sun
Edward is a Security Specialist Solutions Architect focused on identity and access management. He loves helping customers throughout their cloud transformation journey with architecture design, security best practices, migration, and cost optimizations. Outside of work, Edward enjoys hiking, golfing, and cheering for his alma mater, the Georgia Bulldogs.