Fine-grained access control in API Gateway with Cognito access token and scopes


In security and access control, authentication and authorization are two distinct yet interconnected concepts.

Authentication is the process of confirming the identity of a user or system, while authorization defines the actions that the authenticated user is permitted to perform within your system.

Although API Gateway integrates directly with Cognito, it lacks built-in support for fine-grained authorization.

In a previous article, we looked at implementing fine-grained authorization using a Lambda authorizer and Cognito groups. In that approach, authorization takes place on the API side: the caller sends its ID token to the API, and the Lambda authorizer generates a policy based on the user’s groups, which are embedded in the ID token by default.

This time, we’ll look at a different approach – using access tokens with scopes. In this setup, the identity provider (Cognito, in our case) manages both authentication and authorization, offloading these responsibilities from the API.

Customizing Cognito access tokens

As of December 2023, Cognito supports customizing access tokens [1]. Previously, you could only customize the ID tokens with the Pre-Token Generation trigger [2].

This new capability lets you customize the access tokens by adding specific scopes [3]. Here’s how:

1. Enable Advanced Security Features: Turn on this setting in the user pool.

2. Configure the Pre-Token Generation trigger: Choose “Basic features + access token customization” in the “Trigger event version”. Note: CloudFormation doesn’t support this setting and requires manual configuration.

3. Implement the pre-token generation Lambda function: Use this function to add custom scopes to the access token.

Assuming you have two groups in the user pool – Admin and ReadOnly.

You want these groups to correspond to the api/admin and api/readonly scopes, respectively, which will control access to the GET /admin and GET /readonly endpoints.

Your pre-token generation Lambda function might look like the following, where we map the group names to an array of scopes.

The claimsAndScopeOverrideDetails object tells Cognito what scopes to add to the access token. When a user in the Admin group signs in, its access token will look like this:

Map scopes to API Gateway routes

To ensure API Gateway respects these scopes, configure your API Gateway methods with an AuthorizationScopes array.

These scopes are used with a Cognito authorizer to authorize a user request. A user request is authorized if any of the AuthorizationScopes matches a scope in the access token.

Here’s how you can configure the GET /admin and GET /readonly endpoints:

For a working demo, check out this repo [4].

Considerations

This is an elegant solution. There is a lot to like about it. But it has one BIG downside – cost.

The cost implication

Enabling Advanced Security Features significantly increases the cost of using Cognito:

1. 10x increase in cost: Advanced Security Features cost ten times more than the standard monthly active users (MAU) pricing.

2. No free tier: Unlike the standard MAU cost, which includes a generous free tier of 50,000 MAU, there is no free tier for Advanced Security Features.

3. Still have to pay the standard MAU cost: The Advanced Security Features cost is charged in addition to the standard MAU cost, although you benefit from the 50,000 MAU free tier.

The cost overhead is substantial when you enable Advanced Security Features, as detailed in my last post [5].

At 50,000 MAU, you’d pay $2,500 per month, vs. $0 if you use ID tokens for authorization.

At 100,000 MAU, it becomes $4525 (using access tokens) vs. $275 (ID tokens).

Sadly, other vendors would charge you even more, as one of Cognito’s greatest strengths is its cost-efficiency.

Are access tokens more secure than ID tokens?

Proponents of this approach argue that access tokens should be used for authorization and ID tokens for authentication.

While this might be technically accurate, it has little practical impact. As discussed in my last post [5], ID tokens are just as secure as access tokens.

Is this approach faster than Lambda authorizers?

Another argument is that this approach is more performant since it eliminates the need for Lambda authorizers and their associated cold starts.

However, since you still need a Lambda function to customize the access token, the potential for cold starts remains on the critical path.

Conclusion

It’s nice that this approach keeps your authorization logic within the Cognito User Pool setup. It centralizes both authentication and authorization concerns in one place.

However, the cost implications are too significant.

Unless you’re using Advanced Security Features already, or your application has a high value per user (e.g. a B2B enterprise application), this approach may be difficult to justify in terms of return on investment.

Links

[1] Cognito now support the ability to customize access tokens

[2] Cognito’s Pre-Token Generation trigger

[3] How to customize access tokens in Amazon Cognito user pools

[4] Demo repo

[5] Is it safe to use ID tokens with Cognito authorizers?

Related articles


Get 10% off the AWS SA Bootcamp

A friend of this newsletter, Raj Saha (Principal Solutions Architect at AWS), is running a bootcamp to help you land a Solutions Architect job, and has kindly offered us a 10% discount.

AWS SA Bootcamp is the most direct and guided path to become a solutions architect and get a high-paying cloud job in 3 months

  • 10 weeks of live interactive sessions covering system design, hands-on, behavioural, executive conversations, mock interviews, and more, taught by veteran AWS SA and mentor Rajdeep Saha
  • LinkedIn and resume enhancements to get recruiter's attention
  • Previous bootcampers got into top companies including AWS
  • 30 days full money-back guarantee, no questions asked
  • Use the exclusive coupon code “YAN10” to enjoy a 10% discount if you enroll within the next 7 days. Spots are limited.

To learn more about the SA Bootcamp, go to www.sabootcamp.com.

Master Serverless

Join 11K readers and level up you AWS game with just 5 mins a week. Every Monday, I share practical tips, tutorials and best practices for building serverless architectures on AWS.

Read more from Master Serverless

Software systems are getting bigger and more complex. And we are constantly looking for ways to test code in production without risking user experience. Canary deployments is a popular mechanism for rolling out changes incrementally, allowing us to limit the blast radius in case something goes wrong. However, they’re not without limitations. Canary deployments essentially sacrifice a small portion of users for the greater good. But what if you want to gain insights without impacting any real...

A common narrative is that one should always use access tokens to call your APIs, while ID tokens are strictly for identifying users. Some of it has come from this article by Auth0 [1], which makes a strong statement about using ID tokens: However, things are usually more nuanced. In some cases, using ID tokens instead of access tokens is both acceptable and pragmatic. Cognito User Pools might be one of these cases. Cost of using access tokens The common practice amongst Cognito users is to...

In security and access control, authentication and authorization mean two distinct but related things. Authentication verifies the identity of a user or system. Authorization determines what actions an authenticated user is allowed to perform in your system. API Gateway has built-in integration with Cognito, but it doesn’t provide any fine-grained authorization out-of-the-box. By default, a Cognito authorizer only checks if a user’s bearer token is valid and that the user belongs to the right...