AWS Compute Blog

Secure API Access with Amazon Cognito Federated Identities, Amazon Cognito User Pools, and Amazon API Gateway

by Ed Lima | on | in Amazon API Gateway, Amazon Cognito, AWS Lambda |

Ed Lima, Solutions Architect

 

Our identities are what define us as human beings. Philosophical discussions aside, it also applies to our day-to-day lives. For instance, I need my work badge to get access to my office building or my passport to travel overseas. My identity in this case is attached to my work badge or passport. As part of the system that checks my access, these documents or objects help define whether I have access to get into the office building or travel internationally.

This exact same concept can also be applied to cloud applications and APIs. To provide secure access to your application users, you define who can access the application resources and what kind of access can be granted. Access is based on identity controls that can confirm authentication (AuthN) and authorization (AuthZ), which are different concepts. According to Wikipedia:

 

The process of authorization is distinct from that of authentication. Whereas authentication is the process of verifying that “you are who you say you are,” authorization is the process of verifying that “you are permitted to do what you are trying to do.” This does not mean authorization presupposes authentication; an anonymous agent could be authorized to a limited action set.

Amazon Cognito allows building, securing, and scaling a solution to handle user management and authentication, and to sync across platforms and devices. In this post, I discuss the different ways that you can use Amazon Cognito to authenticate API calls to Amazon API Gateway and secure access to your own API resources.

 

Amazon Cognito Concepts

 

It’s important to understand that Amazon Cognito provides three different services:

Today, I discuss the use of the first two. One service doesn’t need the other to work; however, they can be configured to work together.
 

Amazon Cognito Federated Identities

 
To use Amazon Cognito Federated Identities in your application, create an identity pool. An identity pool is a store of user data specific to your account. It can be configured to require an identity provider (IdP) for user authentication, after you enter details such as app IDs or keys related to that specific provider.

After the user is validated, the provider sends an identity token to Amazon Cognito Federated Identities. In turn, Amazon Cognito Federated Identities contacts the AWS Security Token Service (AWS STS) to retrieve temporary AWS credentials based on a configured, authenticated IAM role linked to the identity pool. The role has appropriate IAM policies attached to it and uses these policies to provide access to other AWS services.

Amazon Cognito Federated Identities currently supports the IdPs listed in the following graphic.

 



 

In a nutshell, Amazon Cognito Federated Identities can be compared to a token vending machine that uses STS as a backend. The simplified user authentication flow for a given provider is:

  1. App sends user credentials to provider, usually user name and password.
  2. After the user is authenticated, the provider sends a valid token back to the application.
  3. The application sends the token to the identity pool associated with it.
  4. Amazon Cognito Federated Identities validates the token with the IdP.
  5. If the token is valid, Amazon Cognito Federated Identities contacts STS to retrieve temporary access credentials (access key, secret key, and session token) based on the authenticated IAM role associated with the identity pool.
  6. App contacts the specific AWS service with the temporary credentials.

For more information about the different authentication flows, see the Authentication Flow topic and the Understanding Amazon Cognito Authentication Part 4: Enhanced Flow blog post.

If you don’t want to use an IdP, Amazon Cognito Federated Identities can also support unauthenticated identities by providing a unique identifier and AWS credentials for users who do not authenticate with an IdP. If your application allows customers to connect as a guest user without logging in, you can enable access for unauthenticated identities. In that case, STS sends temporary credentials based on a specific unauthenticated IAM role with appropriate policies. In these cases, AWS strongly recommends that you stick with the principle of the least privilege, only allowing access to perform a certain task and nothing else.
 

Amazon Cognito User Pools

 
Amazon Cognito User Pools, on other hand is a full-fledged IdP that you can use to maintain a user directory and add sign-up and sign-in support to your mobile or web application. It uses JSON Web Tokens (JWTs) to authenticate and validate users. JWT is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

Amazon Cognito User Pools and identity pools can be used in conjunction to provide access to your application. An Amazon Cognito User Pools user authenticated with a user name and password can send a JWT to an associated identity pool. In turn, the identity pool sends temporary AWS credentials back to the application to access other AWS services. There’s no difference with the authentication flow mentioned above for other IdPs.

To use a metaphor, Amazon Cognito User Pools provided you with a passport (JWT) that you can use at the airport counter to retrieve a boarding pass (access credentials) from an identity pool. With your valid boarding pass, you are then allowed to go to the airport gate and board your flight to the AWS Cloud, which is a fitting analogy for this post. The boarding pass is only valid for a specific time. In application terms, the token (passport) authenticates the user and the issued temporary credentials (boarding pass) authorize the access to connect (board).

 

A Practical Example – Integrating Amazon Cognito with API Gateway

 

To demonstrate the different ways that Amazon Cognito User Pools and Amazon Cognito Federated Identities can be used to authorize access to your API Gateway API, use a simple AngularV4 single page web application:

 

 

Here’s the basic concept. You have a sample application that authenticates its users with three different IdPs. You also have an API Gateway API with three different resources (paths), one for each type of user. Each provider can only access one API-specific resource/path and cannot access any other resource allocated to other providers.

After authentication, the user retrieves specific user attributes such as first name, last name, and email details from their provider and sends a request to the API resource (POST) with the data. After access is granted, an AWS Lambda function is invoked to add the details of the specific user to an Amazon DynamoDB table.

You are using a single identity pool and a single API Gateway API to demonstrate that you can secure API access using multiple providers and multiple AuthN/AuthZ options in different ways, but sharing the same resources.

 

 

Both “/google” and “/cip” resources GET/POST methods are configured and secured with IAM authorization. The GET/POST methods in “/cup”, on the other hand, are secured with a user pool authorizer.

You can find the application code and a SAM template with instructions to deploy all the backend services in the aws-cognito-apigw-angular-auth GitHub repository.

There’s yet another way to authenticate API calls with Amazon Cognito: using a Lambda custom authorizer. I’m not covering it in this post; however, the Integrating Amazon Cognito User Pools with API Gateway post showcases this specific use case.

For the first provider, use a public IdP, such as Google. After authenticating with Google credentials, the application collects the user details and then issue a POST call to your API.

 

 

The DynamoDB table is updated with the latest user details and you can retrieve the data about the user making the API call. Notice the ID in blue in the user information card.

 

 

After Amazon Cognito validates a user, it creates a unique identity ID for that user and links it with the specific IdP.

 

 

API Gateway is able to identify the identity ID based on the IAM credentials sent to the API method using the variable $context.identity.cognitoIdentityId in the integration request. A body mapping template adds the ID data automatically to the payload sent to the Lambda function, as follows:

 

#set($inputRoot = $input.path('$'))
{
  "operation": "create",
  "payload": {
      "Item" : {
          "userId" : "$context.identity.cognitoIdentityId",
          "name" : "$inputRoot.name",
          "surname" : "$inputRoot.surname",
          "email" : "$inputRoot.email",
          "provider": "Google"
      }
  }
}

 

Lambda processes the data and sends it in a proper format to DynamoDB, which uses the identity ID as a hash key. Due to the random and unique nature of the ID, it’s perfect to be used for querying the table.

To guarantee that the Google users can only access their specific allocated API resources, attach the following authenticated role to the identity pool. The users assume this IAM role when validated and it explicitly allows only GET (read) and POST (write) access to the API resource “/google”:

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:123412341234:abcde12345/*/GET/google",
                "arn:aws:execute-api:us-east-1:123412341234:abcde12345/*/POST/google"
            ]
        }
    ]
}

 

It’s time to sign in with the second user. This user is authenticated using Amazon Cognito User Pools as IdP. A JWT is used to retrieve temporary AWS credentials from the associated identity pool, same as is used by the Google provider.

Using the Test Access card, you can issue API calls to each one of the three different resources to test the credentials. For instance, if the user tries to make an API call to “/cip” using the IAM credentials provided by Amazon Cognito, the access is granted (Status 200). However, if you try to access the “/google” resource, you get access denied (Status 403). You can confirm using the browser developer tools.

 

 

Even if you have the same identity pool in use by different providers (Google and Amazon Cognito User Pools), you can associate another IAM role to a group with Amazon Cognito User Pools. Because the user “jdoe” is part of that group, it inherits the IAM role association.

 

 

The IAM role explicitly allows only GET (read) and POST (write) access to the API resource “/cip”, nothing else.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:123412341234:abcde12345/*/GET/cip",
                "arn:aws:execute-api:us-east-1:123412341234:abcde12345/*/POST/cip"
            ]
        }
    ]
}

 

The role details are passed in the JWT itself. For that reason, the identity pool allows the role to be assumed. The default authenticated role for the identity pool needs to be DENIED or else your user would also have access to the “/google” resource.

 

 

For the third and final user, skip Amazon Cognito Federated Identities altogether and authenticate the user from the Amazon Cognito User Pool directly to API Gateway using a Cognito user pool authorizer. You can find more information on my previous blog post, Authorizing Access Through a Proxy Resource to Amazon API Gateway and AWS Lambda Using Amazon Cognito User Pools.

The user can only access the API resource assigned to the user pool and it’s denied for the other API paths:

 

 

The ID Token (JWT) is sent directly to API Gateway and there’s no IAM role involved in the user validation. The ID in the user information card looks different compared to the previous users. As no cognito identity ID is generated in this scenario, you need another type of unique ID for the user.

Test the JWT generated in this particular user session in the API Gateway console, and retrieve user attributes and claims:

 

 

The “sub” attribute is a unique identifier generated by Amazon Cognito User Pools and can be used to track each user from the user pool. You use this attribute as a hash key for the user pool users in the DynamoDB table. Use the variable $context.authorizer.claims.sub in the integration request, and the following body mapping template adds the ID data automatically to the payload sent to the Lambda function:

 

#set($inputRoot = $input.path('$'))
{
  "operation": "create",
  "payload": {
      "Item" : {
          "userId" : "$context.authorizer.claims.sub",
          "name" : "$context.authorizer.claims.given_name",
          "surname" : "$context.authorizer.claims.family_name",
          "email" : "$context.authorizer.claims.email",
          "provider" : "Cognito User Pools"
          }
  }
}

 

Check the DynamoDB console and confirm that all users were successfully added to the table with the API calls. As each user can only add or retrieve data from their specific API resource and only using their own UserID, the data is unavailable to other users.

 

 

Summary

 

Amazon Cognito provide a rich set of features to authenticate and authorize users. You can take advantage of the built-in flexibility and different options to secure access to your API Gateway API integrating with multiple IdPs.

There are distinct and specific use cases to authenticate, secure, and provide access to your API leveraging the integration between Amazon Cognito and API Gateway, depending on the work load and implementation of your application. I covered a couple of different scenarios in this post:

  • Your users are defined and authenticated by an external or public IdP.

You used Google for your sample application but it could be Facebook, Twitter, Amazon, OpenID Connect, your own developer IdP, or even an enterprise-level SAML provider, such as Active Directory. Use any IdP that can seamlessly integrate with Amazon Cognito Federated Identities linked with AWS Identity and Access Management roles.

  • Your users are defined in your own IdP powered by Amazon Cognito User Pools, leveraging aditional secure access with IAM permissions.

You want to have additional access granularity and security by integrating the authentication with IAM role-based access controls and Amazon Cognito User Pools groups.

  • Your users are defined in your own IdP powered by Amazon Cognito User Pools, leveraging secure JWTs for authentication.

You want to authenticate your API calls directly and seamlessly using these tokens and you are not interested in using IAM integration to authorize access with IAM roles.

With Amazon Cognito User Pools, there’s no need to worry about the undifferentiated heavy lifting of maintaining your own IdP servers, allowing you to focus on application logic instead. It also gives you more control around your users, groups, applications, and attributes with no dependency on an external public provider.

It’s a great balance between control and ease of use as it integrates seamlessly with identity pools for specific scenarios that require the additional use of IAM roles for granular security but can also work directly with JWTs. In addition to all these advantages, you can also integrate Lambda triggers for different sign-in, sign-up, confirmation, validation, and verification workflows to customize and adapt the user identification process even further.

The integration between Amazon Cognito and API Gateway allows great flexibility such that you can implement these different authentication scenarios separately or even use all of them in conjunction (as demonstrated with the sample web application). You can provide access to the different API users and resources based on a specific IdP or multiple providers of your choice.