Microsoft Workloads on AWS

Secure API authorization in Amazon API Gateway using Microsoft Entra ID

In this blog post, we will guide you through the process of setting up an AWS Lambda authorizer with Microsoft Entra ID (formerly Azure Active Directory) using OpenID Connect (OIDC). This will allow you to use the authentication from Entra ID as an identity provider for your Amazon API Gateway.

Securing your APIs is crucial for protecting sensitive data and ensuring that only authorized users can access your resources. With AWS Lambda authorizer and Amazon API Gateway, you have access to a powerful solution for implementing API authorization and effectively controlling access to your APIs.

By combining the serverless compute power of AWS Lambda with the identity and access management features of Entra ID, you can establish a streamlined and scalable authorization flow for your APIs. With this integration, you can use new or existing user accounts in Entra ID to support authentication and authorization for your API users.

Solution overview

We will guide you through the step-by-step process of users requesting a resource which get authenticated and authorized through an AWS Lambda authorizer fetching credentials from Microsoft Entra ID to build a secure authorization flow. This comprehensive guide will equip you with the knowledge and practical insights needed to implement this authentication and authorization solution. Figure 1 shows the high-level AWS architecture diagram and services used in the solution:

1. Amazon API Gateway

2. AWS Lambda

3. AWS Identity and Access Management (IAM)

AWS Lambda authorizer API authorization flowFigure 1: AWS Lambda authorizer API authorization flow

These are the high-level steps used to establish API authorization with an AWS Lambda authorizer and Entra ID integration:

  1. Set up Entra ID configuration: Configure Entra ID as the identity provider and establish trust between Entra ID and AWS services.
  2. Build an AWS Lambda authorizer: Create an AWS Lambda authorizer function for Amazon API Gateway to validate the JSON Web Token (JWT) and return IAM policy in response with access permissions to take required actions.
  3. Build an AWS Lambda response function: Create AWS Lambda function as API Gateway response for testing this solution.
  4. Configure Amazon API Gateway with the AWS Lambda authorizer: Integrate the AWS Lambda authorizer function with Amazon API Gateway to support the authorization flow for API endpoints.
  5. Testing and troubleshooting: Test and troubleshoot common issues by simulating API requests and verify the authorization using Entra ID access tokens by invoking the Amazon API Gateway API through API platforms like Postman.

Prerequisite

To complete this solution, you need administrative access to Entra ID and an AWS account. If needed, you can follow these instructions to set up Entra ID access.

Walkthrough

Step 1: Set up Entra ID configuration

1.Navigate to the Entra ID console.

2. From the Expose an API tab, select Add a scope, as shown in Figure 2.

Entra ID App registration portal

Figure 2: Entra ID App registration

3. Provide the Scope name and Save, as shown in Figure 3. Once created, copy and note this value for future use.

Entra ID scope definitionFigure 3: Entra ID scope definition

4. Scope, as shown in Figure 4, helps enforce the principle of least privilege, ensuring that applications only have access to the specific resources and actions they need. When an application wants to access a protected resource, it requests an access token from Azure Active Directory (AD). In the token request, the application specifies the scopes it needs to perform its intended actions. In this case, you will invoke Amazon API Gateway, which in turn will return a Lambda response.

Entra ID custom scope valueFigure 4: Entra ID custom scope

5. From the Certificates & secrets tab, create a Client secrets, as shown in Figure 5.

Entra ID App attribute – Client Secret valueFigure 5: Entra ID App attribute – Client Secret

6. Once created, please copy and note the Value and Secret ID for future use, as shown in Figure 6. Microsoft Azure client secrets are confidential credentials used for authenticating applications and services with Azure AD. You will use them to grant access to AWS resources on behalf of Entra ID services.

Entra ID App – Client Secret Key Value PairFigure 6: Entra ID App – Client Secret Key Value Pair

Step 2: Build an AWS Lambda authorizer

An AWS Lambda authorizer is a feature provided by AWS that acts as a gatekeeper to centralize authorization logic, ensuring that only authenticated and authorized users can access API resources. The AWS Lambda authorizer integrates with AWS services like API Gateway and IAM, which allows you to control access to your APIs by validating and authorizing incoming requests. It also returns an IAM policy with allowed access permissions to take required API actions.

AWS Lambda supports two types of authorizers: token-based authorizers and request-based authorizers. This walkthrough is using token-based authorizers, which relies on a JWT token to validate the request. The token is usually provided in the “Authorization” header using the “Bearer” scheme. It authenticates clients and determines resource access by verifying authorization tokens, constructing an authorization policy, and sending it to the Amazon API Gateway for evaluation. You can read about other supported authorizers from the AWS Lambda authorizer developer guide.

Here are the steps to build an AWS Lambda authorizer:

  1. You will use the aws-jwt-verify library as an AWS Lambda layer to decode and validate JWT token to authorize user, which is a JavaScript library for verifying JWTs signed by any OIDC-compatible IDP that signs JWTs with RS256 / RS384 / RS512.
  2. To use this library, you will need to download and install it locally. Run npm install aws-jwt-verify command from your local computers terminal (macOS) or Command prompt/PowerShell (Windows). This will generate a node module file. Compress and zip the entire module.
  3. Log in to the AWS Management Console and navigate to AWS Lambda. From the left menu panel, select Layers, and click on Create Layer, as shown in Figure 7.

Adding AWS Lambda layer from AWS Management ConsoleFigure 7: Adding AWS Lambda layer from AWS Management Console

4. On the Create Layer page, as shown in Figure 8, specify Name (for example, aws-jwt-verify) and Description to your layer and Upload the .zip file you created in step 2 above. For Compatible runtimes, add Node.js runtimes 18.x and higher.

Figure 8: aws-jwt-verify module as AWS Lambda layer

5. Next, Create function with name “lambda-authorizer-for-azure-ad” and choose the runtime as Node.js 20.x, as shown in Figure 9.

Figure 9: Create AWS Lambda Function

The function receives a bearer token as an event, which is then passed to the aws-jwt-verify method to decode and validate the JWT token. After successful verification, this method extracts the user’s scope claim to determine the user’s access permissions to AWS services.

6. Add the layer “aws-jwt-verify”, which we created in step 4, as shown in Figure 10.

Add layer to AWS Lambda function Figure 10: Add layer to AWS Lambda function

7. On the Add layer, select Custom layers, as shown in Figure 11. From the custom layers list, select the layer that we created in step 4 and select latest version (version 1). Click Add to add the layer in the function.

Add custom layers to AWS Lambda functionFigure 11: Add custom layers to AWS Lambda function

8. Copy and replace the code below in your Code source index.mjs file:

Note: Please update IDP verifier parameters placeholder (<issuer_url_placeholder>, <audience_id_placeholder>) and iamPolicytoInvokeAPI policy Resource (<account-id>:<api-id>) with appropriate values to match with your environment.

// Import aws-jwt-verify library (https://github.com/awslabs/aws-jwt-verify) as a part of the Lambda Layers.
import { JwtRsaVerifier } from "aws-jwt-verify";
import { JwtExpiredError } from "aws-jwt-verify/error";
import { JwtInvalidClaimError } from "aws-jwt-verify/error";

// IAM default policy to deny all.
  const defaultDenyAllPolicy = {
      "Version":"2012-10-17",
      "Statement":[
        {
          "Action":"execute-api:Invoke",
          "Effect":"Deny",
          "Resource":"*"
        }
      ]
  };
  
// IAM policy to invoke API.
  const iamPolicytoInvokeAPI = {
      "Version":"2012-10-17",
      "Statement": [
        {
          "Action": "execute-api:Invoke",
          "Effect": "Allow",
          "Resource": "arn:aws:execute-api:us-east-1:<account-id>:<api-id>/*/*" //update accountID and API ID to match with your AWS API Gateway environment 
        }
      ]
  };
  
/*
   Main Lambda handler functions.
*/  
export const handler = async(event) => {

    let response = {
            "principalId": "user",
            "policyDocument": defaultDenyAllPolicy
          };
    
    // Define IDP verifier parameters
    const verifier = JwtRsaVerifier.create({
      issuer: "https://sts.windows.net/<issuer_url_placeholder>/", //replace <issuer_url_placeholder> with Entra ID issuer url 
      audience: "api://</audience_id_placeholder>", //replace <audience_id_placeholder> with Entra ID audience api 
      jwksUri: "https://login.microsoftonline.com/common/discovery/keys"
      });
      
    try {
        
      // Get the authorization token from the request headers
      const authorizationHeader = event.headers.authorization;
        
      // Check if the Authorization header is present
      if (authorizationHeader) {
        
        if (!authorizationHeader.startsWith('Bearer ')) {
          console.error("Invalid Bearer Scheme");
          return response;
        }
        else {
          const token = authorizationHeader.replace("Bearer ", "");
          const payload = await verifier.verify(token);
            
          // Get the user permissions scope from the token to authorize user for specific actions
          const scopeClaims =  payload.scp;
            
          if (scopeClaims != null) {
              response = {
                "principalId": "user",
                "policyDocument": iamPolicytoInvokeAPI
              };
          }
        }
      }
      else {
        console.error("Authorization Header Missing");
        return response;
      }
    }
    catch (err) {
        console.error(err);
        return response;
    }
    
  return response;
};

9. From the Code Source tab, select Deploy to deploy the Lambda function code, as shown in Figure 12.

AWS Lambda function code deployFigure 12: AWS Lambda function code deploy

Step 3: Build an AWS Lambda response function

In this step, you will create another Lambda function as output for the Amazon API Gateway response. This function will be used later in the AWS API Gateway setup as a sample response function for testing.

1. Log into the AWS Management Console and navigate to AWS Lambda.

2. Select Create function and choose Author from scratch.

3. Enter a function name. In this walkthrough, I am using sample-lambda-function. Choose the runtime as Node.js 20.x. Leave the default code as is and proceed to next step.

Step 4: Configure Amazon API Gateway with the AWS Lambda authorizer

1. Log in to the Amazon API Gateway Console and select HTTP API as an API type, as shown in Figure 13. Click Build.

Amazon API Gateway consoleFigure 13: Amazon API Gateway console

2. Enter the API name. For this walkthrough, I have named it http-api-for-auzuread-auth.  Select Review and Create, as shown in Figure 14.

Create Amazon API Gateway API Figure 14: Create Amazon API Gateway API

3. To add a route, select Routes from the left navigation pane and click Create, as shown in Figure 15.

API Gateway RouteFigure 15: API Gateway Route

4. In the Route and method section, select GET as method and type /auth as a sample path. Click Create, as shown in Figure 16.

Create a routeFigure 16: Create a route

5. From the left navigation menu, select Authorization. Select Get route from the list and then Create and attach an authorizer, as shown in Figure 17.

API Gateway Authorization Figure 17: API Gateway Authorization

6. On the Create authorizer page, select Lambda as an authorizer type, provide a Name, select the AWS Lambda authorizer function created in step 3.a, and select IAM Policy as response mode . In Invoke permission, make sure to select Automatically grant API Gateway permission to invoke your Lambda function. Keep the rest as defaults and proceed with Create and attach, as shown in Figure 18.

(Optional): You can enable API caching in Amazon API Gateway to cache your endpoint’s responses and improve request latency. Cached responses for a specified time-to-live (TTL) period are used to respond to requests, reducing the number of calls to the endpoint.

Note: Managing cache invalidation is critical because if the lambda authorizer code or the data used for authorization changes, the cache needs to be invalidated to ensure that subsequent requests are correctly authorized.

Amazon API Gateway authorizer setupFigure 18: Adding AWS Lambda as an authorizer

7. From the left navigation menu, select Integrations. Select Get route from the list and then Create and attach an integration, as shown in Figure 19.

Amazon API Gateway IntegrationFigure 19: Amazon API Gateway Integration

8. On the Create an integration page, select Lambda function as an integration type, then choose the sample Lambda response function created in step 4. In Invoke permission, make sure to select Automatically grant API Gateway permission to invoke your Lambda function. Proceed with Create, as shown in Figure 20.

Create an Amazon API Gateway IntegrationFigure 20: Create an Amazon API Gateway Integration

5. Testing and troubleshooting

You can test this API using an API development tool, such as Postman:

1. From the Entra ID console, navigate to the Entra ID app created in step 1 for this walkthrough and select Endpoints.

Entra ID App Overview page Figure 21: Entra ID App Overview page

2. Copy the values of OAuth 2.0 authorization endpoint (v2) and OAuth 2.0 token endpoint (v2) as Auth URL and Access Token URL respectively, as shown in Figure 22. Use these values in the Postman request.

Entra ID App EndpointsFigure 22: Entra ID App Endpoints

3. In the Authorization tab of Postman request, enter Auth URL, Access Token URL, Client ID, Client Secret, and Scope values to generate new access token, as shown in Figure 23.

Postman interface to generate tokenFigure 23: Postman interface to generate token

Once you are authorized and receive a token, you can use the token to test API various endpoints. You will receive “200” status for all authorized APIs.

It is important to note that if different scopes or permissions are required for different parts of the API, they must be added to the scopes in Entra ID, which must be space delimited. When the AWS Lambda authorizer is configured as your authorization source, you can only access the resource if you supply a valid token with the valid scope.

Cleanup

  • In the Entra ID Environment, delete the Microsoft Entra ID setup we created as a part of this solution.
  • In your AWS environment, log in to Amazon API Gateway Console and delete HTTP API and the AWS Lambda authorizer function.

Conclusion

In this blog post, we walked you through the process of setting up an AWS Lambda authorizer for API authorization using Entra ID to create a robust and scalable authentication solution for API access control. This step-by-step guide covered understanding AWS Lambda authorizer, configuring Entra ID for API authorization, building the AWS Lambda authorizer function, integrating AWS API Gateway and Entra ID, testing and troubleshooting the setup, and scaling and monitoring the solution. Furthermore, similar to the Amazon API Gateway integration, the AWS Lambda authorizer can be configured with other AWS services like AWS AppSync to integrate with Entra ID users to access AWS services and endpoints.

AWS has significantly more services, and more features within those services, than any other cloud provider, making it faster, easier, and more cost effective to move your existing applications to the cloud and build nearly anything you can imagine. Give your Microsoft applications the infrastructure they need to drive the business outcomes you want. Visit our .NET on AWS and AWS Database blogs for additional guidance and options for your Microsoft workloads. Contact us to start your migration and modernization journey today.

Mugdha Vartak

Mugdha Vartak

Mugdha Vartak is a Solutions Architect at AWS. She works closely with mid-large enterprise customers from various industries to help them accelerate their AWS cloud adoption. She is responsible for designing, building, and migrating scalable and resilient architectures within the AWS cloud.

Amey Bhavsar

Amey Bhavsar

Amey Bhavsar is a Senior Solutions Architect at AWS based of New York, specializing in guiding enterprise clients across diverse industries. As a core member of the Next Gen Developer Experience TFC, Amey accelerates AWS cloud adoption by designing and implementing scalable and resilient architectures. His innovative solutions ensure a seamless transition to the cloud, demonstrating a commitment to excellence and deep expertise in AWS technologies.

Santosh Mohanty

Santosh Mohanty

Santosh Mohanty is a Solutions Architect at AWS located in New York. He works with enterprises from all industry verticals to design and implement a variety of solutions in the AWS Cloud. He is an engineer at heart, and is passionate about solving customer challenges with innovative solutions. In addition to keeping up-to-date with emerging technologies, he loves traveling and biking.