Skip to content

miztiik/serverless-api-authorizer

Repository files navigation

Serverless API Authorizer

Authentication and Authorization are tough problem almost. Doing it right is not easy. You need to balance usability and security. When you are building serverless application, like an API, you would want your APIs to be accessible by authorized users.

Let us try and understand this with an example. Our start-up Miztiik Corp is a content publisher. Their platform leverage's serverless components like AWS Lambda & published their endpoints using AWS API Gateway. They want to authorize all access to their APIs serving premium content. They have adopted OAuth for the authorization as that gives them the flexibility to integrate with multiple identity providers. The authorization is handled AWS Cognito using OAuth.

The appropriate authorization flow for Machine-To-Machine or API-To-API communication is called OAuth2 Client Credentials, there is no user, or identity associated with the access request. The calling service obtains an access token, and the target service asserts that token to be valid before granting access to the protected data. The process is fairly straightforward,

  1. The machine (i.e. script/Lambda/Requester) authenticates itself against a Cognito Endpoint with a list of desired scopes
  2. Cognito verifies the credentials and checks if the machine is allowed to get these scopes
  3. If the credentials are valid and the scopes can be granted, Cognito returns an Access Token to the machine
  4. The machine can use that Access Token to Authenticate itself against the API-Gateway or an Application Load Balancer

Scopes are technically part of authorization. A scope more or less is a label that describes a capability such as READ_CONTENT or WRITE_CONTENT. You can create as many scopes as you like, but without further processing they are useless.

From the perspective of an App you get information about which scopes the current request has been granted and you as the app owner is responsible for implementing authorization measures based on that.

Miztiik Serverless API Authorization

In this article, we will build the above architecture. using Cloudformation generated using AWS Cloud Development Kit (CDK). The architecture has been designed in a modular way so that we can build them individually and integrate them together. The prerequisites to build this architecture are listed below

  1. 🧰 Prerequisites

    This demo, instructions, scripts and cloudformation template is designed to be run in us-east-1. With few modifications you can try it out in other regions as well(Not covered here).

    • 🛠 AWS CLI Installed & Configured - Get help here
    • 🛠 AWS CDK Installed & Configured - Get help here
    • 🛠 Python Packages, Change the below commands to suit your OS, the following is written for amzn linux 2
      • Python3 - yum install -y python3
      • Python Pip - yum install -y python-pip
      • Virtualenv - pip3 install virtualenv
  2. ⚙️ Setting up the environment

    • Get the application code

      git clone https://github.com/miztiik/serverless-api-authorizer.git
      cd serverless-api-authorizer
  3. 🚀 Prepare the dev environment to run AWS CDK

    We will cdk to be installed to make our deployments easier. Lets go ahead and install the necessary components.

    # If you DONT have cdk installed
    npm install -g aws-cdk
    
    # Make sure you in root directory
    python3 -m venv .env
    source .env/bin/activate
    pip3 install -r requirements.txt

    The very first time you deploy an AWS CDK app into an environment (account/region), you’ll need to install a bootstrap stack, Otherwise just go ahead and deploy using cdk deploy.

    cdk bootstrap
    cdk ls
    # Follow on screen prompts

    You should see an output of the available stacks,

    cognito-identity-provider
    premium-content-provider
    content-consumers-stack
  4. 🚀 Deploying the application

    Let us walk through each of the stacks,

    • Stack: cognito-identity-provider In this scenario, Cognito’s User Pool is merely a placeholder, as we will have no users. The only user will be the app client. Miztiik Serverless API Authorization This stack creates the following resources,

      • AWS Cognito Identity Pool to store identities
      • Cognito domain - to host the OAuth2 endpoint, /oauth2/token
      • An app client. Our content consumers will be using this app client to access premium content
      • Resource Server with custom scopes for read and write
      • We will be using the client_credentials OAuth flow, as we our scenario fits the machine-to-machine communication
      • We will store the app_secrets information like app client id, app client secret and resource server identifier and app domain in AWS Secrets Manager, which you can securely share with the downstream API consumer

      Initiate the deployment with the following command,

      cdk deploy cognito-identity-provider
    • Stack: premium-content-provider Miztiik Serverless API Authorization This stack creates the following resources,

      • A Lambda function that generates content dynamically. Supports
        • read scope for GET requests
        • write scope for POST requests
      • API Gateway Authorizer integrated with the cognito-identity-provider
      • API Gateway to validate the requests for GET & POST methods using cognito. Upon successful validation trigger Lambda and return response to requester.

      Initiate the deployment with the following command,

      cdk deploy premium-content-provider

      Check the Outputs section of the stack to access the PremiumApiUrl

    • Stack: content-consumers-stack

    Miztiik Serverless API Authorization

    Our consumers will have two different flows

    • Unauthorized Access
    • Authorized Access

    We will simulate them using AWS Lambda, triggered from API Gateway.

    This stack creates the following resources,

    • A Lambda function that can send different types of requests.
      • {API_URL}/unauthorized-read
      • {API_URL}/authorized-read
      • {API_URL}/authorized-write
    • API Gateway to make it easier for us to trigger the lambda, simulating an end user

    For each of the authorized-{scope} invocation, the lambda will make an request to cognito-identity-provider get the access_token. The request to cognito is faciliated by the app_secrets stored in AWS Secrets Manager by our cognito-identity-provider stack.

    Once we have the access_token we trigger the request to premium-content-provider

    Initiate the deployment with the following command,

    cdk deploy content-consumers-stack

    Check the Outputs section of the stack to access the UnauthorizedUrl, AuthorizedReadUrl and AuthorizedWriteUrl

  5. 🔬 Testing the solution

    The Outputs section of the content-consumers-stack stack has the required information on the urls

    • Access the UnauthorizedUrl in your browser. This will trigger an request to the premium_api without any authorization. This request is rejected by the API Gateway and you get an unauthorized message as response.

    Expected Output,

    {
      "message": "Unauthorized"
    }
    • When you access the AuthorizedReadUrl, The following happens,
      • The consumer(lambda) retrieves the app secrets from AWS Secrets Manager
      • Send an POST request to cognito with secrets. Cognito validates the app id, app secret and generates a auth token for the given scope (read in this case)
      • With this auth token make an GET request to premium api
      • API Gateway validates the auth token and on successful validation invokes the premium content lambda
      • Return the response back to requester.

    Expected Output,

    {
      "message": "Premium Content: OAuth Scope: Read"
    }
    • When you access the AuthorizedWriteUrl, The following happens,
      • The consumer(lambda) retrieves the app secrets from AWS Secrets Manager
      • Send an POST request to cognito with secrets. Cognito validates the app id, app secret and generates a auth token for the given scope (write in this case)
      • With this auth token make an POST request to premium api
      • API Gateway validates the auth token and on successful validation invokes the premium content lambda
      • Return the response back to requester.

    Expected Output,

    {
      "message": "Premium Content: OAuth Scope: Write"
    }

    You can check the logs in cloudwatch for more information or increase the logging level of the lambda functions by changing the environment variable from INFO to DEBUG

  6. 🧹 CleanUp

    If you want to destroy all the resources created by the stack, Execute the below command to delete the stack, or you can delete the stack from console as well

    • Resources created during deployment
    • Delete CloudWatch Lambda LogGroups
    • Any other custom resources, you have created for this demo
    # Delete from cdk
    cdk destroy cognito-identity-provider
    
    # Follow any on-screen prompts
    
    # Delete the CF Stack, If you used cloudformation to deploy the stack.
    aws cloudformation delete-stack \
        --stack-name "MiztiikAutomationStack" \
        --region "${AWS_REGION}"

    This is not an exhaustive list, please carry out other necessary steps as maybe applicable to your needs.

📌 Who is using this

This repository teaches developers, Solution Architects & Ops Engineers how to build complete architecture in AWS. Based on that knowledge these Udemy course #1, course #2 have been created to enhance your skills.

💡 Help/Suggestions or 🐛 Bugs

Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional documentation or solutions, we greatly value feedback and contributions from our community. Start here

👋 Buy me a coffee

ko-fiBuy me a coffee ☕.

📚 References

  1. Control access to a REST API using Amazon Cognito User Pools as authorizer

  2. Authorize API access using custom scopes in Amazon Cognito

  3. Allow users to invoke API Gateway REST API/Lambda using the execution role from an Amazon Cognito user pool group

  4. Server to Server Auth with Amazon Cognito

  5. AWS API Gateway - using Access Token with Cognito User Pool authorizer?

🏷️ Metadata

Level: 300