API Security for Modern Web Apps

When you’re building new web applications and simultaneously trying to grow your customer base, there are a lot of competing concerns you need to balance. Figuring out how to secure your application and manage users is certainly important, but rarely will these features truly differentiate your product. These days customers expect a seamless registration and log-in experience, but writing all the code to support this functionality yourself can be extremely time consuming and expensive. When you’re operating with limited resources and tight timelines, finding ways to shift this development burden to existing tools is a must. Enter
Amazon API Gateway.

API Gateway is a new service that makes it easy to create and publish RESTful APIs in the cloud. This service provides a number of features for managing APIs, but in this post I’ll focus on the tools and approaches you can use to create flexible and robust security controls for the interfaces you host on API Gateway.

This post discusses security concerns associated with enabling user access to public APIs. The approach of exposing and securing RESTful endpoints over the Internet with API Gateway makes it easy to develop a wide variety of client applications specialized for different platforms and devices. By using these tools, it’s easy to maintain a consistent and robust security layer that integrates seamlessly across various user apps.

Authentication and Authorization Flow

For the purposes of illustration, imagine you have a service for running a pet store (see the Auth0 tutorial referenced below to run this sample in your own account). This service exposes a /pets resource where the GET method retrieves a list of pets for sale and the POST method adds new pets to the list. You want to ensure that only the administrators of your store can add new listings, but anyone who is logged in should be able to see the list of pets for sale.

When planning for a secured system like this, you need to consider two fundamental concerns: authentication and authorization. Authentication is how you confirm that a user is who he says he is, and authorization is how you determine what that user is allowed to do. Continuing with the pet store example, assume Alice is an administrator and Bob is a legitimate customer. If a malicious user, Mallory, tries to pretend to be Alice in order to post fake listings, she would fail at the authentication step. If Bob logs in as himself and tries to post a listing, he would fail at the authorization step because customers don’t have that level of permission.

When it comes to securing individual API Gateway methods, you can manage both of these concerns through the AWS Identity and Access Management (IAM) service. In this context, the authentication process is about enabling your users to exchange their username and password for a set of IAM credentials that are used to authenticate individual API requests. API Gateway then handles the authorization process by using the IAM policies associated with those credentials

In the following sections, I discuss approaches for integrating third-party authentication providers with API Gateway as well as the basics of configuring authorization for your APIs.

User Authentication

As noted in the overview, to access an API hosted on API Gateway your users will need to have a set of IAM credentials. However, providing long-lived IAM credentials directly to all of your users is considered a very bad security practice. It’s difficult to manage provisioning and revocation, not to mention the user experience would be quite clunky.

Instead, a better approach is to use an external identity provider to manage user authentication and fetch temporary IAM credentials for making API calls. The identity provider can also handle all the associated administrative tasks such as user provisioning, password resets, and so on. For public-facing Internet services, it often makes sense to integrate with a public identity provider such as Amazon or Facebook, but you can also use other OpenID Connect providers or write your own authentication service backed by a proprietary database.

Regardless of which identity providers you choose to integrate with, you’ll also need a mechanism for exchanging a user’s externally authenticated identity for a set of IAM credentials. There are a variety of ways to accomplish this, but in the remainder of this post I focus on the following:

Amazon Cognito

Amazon Cognito allows you to easily integrate with a variety of third-party authentication providers and takes care of integrating with AWS Security Token Service (STS) in order to fetch temporary IAM credentials. When using Amazon Cognito, you manage the users for your application using an identity pool. These let you define one or more identity providers that will take care of user authentication as well as an IAM role that any authenticated user can assume.

For example, you may choose to allow users to log in to your app using Login with Amazon, Google+ Sign-in, or a username and password specific to your app. To implement this with Amazon Cognito, you create a new identity pool and configure Amazon, Google, or a custom backend as authentication providers. Then you can use the GetId and GetCredentialsForIdentity methods from the Amazon Cognito API to exchange an identity from one of the authentication providers for the IAM credentials required to make API calls, as shown in the following illustration.

SAML Providers

Whereas Amazon Cognito allows you to map a single role to all of the authenticated users in an identity pool, SAML providers enable more granular mappings between classes of users and roles. Assuming you have separate IAM roles for each type of user who accesses your API, you can maintain both your user identities and which roles they have access to within the identity provider itself.

When users log into your application, they first authenticate against the SAML provider, which gives them a SAML token. This token includes information about a user’s identity, a list of IAM roles the user is allowed to assume, and a cryptographic signature that proves the validity of the token. The token is then exchanged for temporary IAM credentials using the AssumeRoleWithSAML method from the AWS STS API. Finally, requests are made to your API using these credentials, as shown in the following illustration.

Third-party Solutions

In addition to services such as Amazon Cognito provided by AWS, there are a number of third-party offerings within the broader AWS partner ecosystem that can help you manage complex integrations across multiple identity providers and IAM roles. One such partner, Auth0, recently published a tutorial that shows different methods of integrating security with API Gateway, including the use of custom rules that map between identities and IAM roles. This allows you to provide different levels of access for different classes of users based on a variety of attributes. The following diagram shows the integration using Auth0 delegation for AWS that the tutorial demonstrates.

Authorization

Once you’ve set up a mechanism for your users to log in and obtain IAM credentials, you still have to define what operations a user is allowed to perform.

Method-level Authorization

To manage access to individual methods on your API, API Gateway uses the IAM credentials obtained during the authentication phase to authorize requests to each method. Each set of credentials is associated with a set of policies that define the permissions granted to the user. By modifying these policies, you can affect which methods on your API each user is able to access.

For example, assume there is a “product” resource that is readable by all users, but writable only for administrators. In this case you would have two IAM roles — one for administrators and one for regular users — and you would assign policies to each with the following statements:

Within IAM policies that control access to your API, the various HTTP methods (GET, POST, PUT, etc.) are actions and each RESTful resource in your API is a resource with its own ARN. By combining statements that allow or deny a set of HTTP methods on each of your API resources, you can craft precise policies that grant access to the minimal set of methods required for each user class.

Fine-grained Permissions

Setting permissions at the method level allows you to control which high-level actions a user is allowed to access based on the user’s role, but in some cases you will need to manage more granular control over what specific data a user is allowed to operate on. For instance, you might allow all authenticated users to POST to the account resource to modify their account details, but each user should be limited to modifying only his account and no one else’s.

To accomplish this, the services you write need access to the identity of the original caller. You can’t rely on a raw value in the request to identify the caller because any user with permission to invoke a method could potentially spoof the identity of another. Instead, a common solution is to pass a secure token obtained from the authentication provider through the API to the backend service. The service can validate the token with the authentication provider, and then make a decision about whether to allow the operation. With this approach, you must embed the fine-grained authentication controls into your application logic itself.

Conclusion

Securing applications and APIs is a complex subject. I hope this post provides a good overview of some common approaches for managing user access to your APIs using API Gateway and IAM. I encourage you to explore the other resources available for managing security on AWS, including our security blog and the IAM and AWS STS documentation to ensure you’re following best practices when it comes to this critical aspect of application design and implementation.