Want to learn more about AWS and Projects? Sign up for my mailing list!

Today is project twelve from my Twenty Projects in Twenty Days series! Yesterday, I published Voices of COVID which is a project aimed at hearing the voices of people impacted by COVID-19. Today, I’m looking at how to create an AWS HTTP API that has JWT authorizers with Amazon Cognito and Lambda handlers written in Node.js. If you want a more in-depth look at this you can take a look back at how I did this with the Serverless Framework in this blog post.

Let’s get started!

Prerequisites

  1. You’ll need to get the code from GitHub here
  2. You’ll also need the AWS CLI installed and configured
  3. Then you’ll need to install the Serverless Framework
  4. For this demo, I’ll also be using Node.js but you technically don’t need it locally to deploy the service

Setting up the HTTP API

In order to get an AWS HTTP API setup in AWS we could manually configure it in the AWS console or with the AWS CLIs. Instead, I opted to use the Serverless Framework to take care of this for us. We can do this by setting up an HTTP API event for a Lambda Function in the serverless.yml file.

First, we need to setup a the service details at the top with a service name and potentially an org and app if we’re using Framework Pro.

org: yourorg # optional
app: yourapp # optional
service: http-api-node

From there, we have a provider section where we setup the details for our cloud (AWS), our runtime (Node.js), and any environment variables we’re using.

provider:
  name: aws
  runtime: nodejs12.x
  environment:
    DOMAIN_SUFFIX: your-unique-suffix
  # ...

In this case, the DOMAIN_SUFFIX environment variable should be replaced with something unique so that when you try to create Cognito resources later the names that you use for them won’t conflict with resources other people like me have already created. The next part of this document is specifying that our HTTP API will be using Cognito Authorizers:

  # ...
  httpApi:
    authorizers:
      serviceAuthorizer:
        identitySource: $request.header.Authorization
        issuerUrl: 
          Fn::Join:
          - ''
          - - 'https://cognito-idp.'
            - '${opt:region, self:provider.region}'
            - '.amazonaws.com/'
            - Ref: serviceUserPool
        audience:
          - Ref: serviceUserPoolClient

In this section, we provide more details about the httpApi resource that will be used in our service. We give this API an authorizer that has an identity source of the Authorization header.

Then we provide it an issuerUrl to verify against. In this case, the issuerUrl is a combination of the ID of a serviceUserPool resource that we’ll create in a moment and a standard formula for creating these URLs that starts with https://cognito-idp. and then has an AWS region like us-east-1 followed by .amazonaws.com/ and finally the ID of the serviceUserPool. The Fn:Join: and the extra - characters and all that are just messy YAML needed to make all those values come together properly.

Finally, we have the audience, which in this case is the id of the serviceUserPoolClient which we create in a moment too.

Next up, we actually configure the HTTP API as an event trigger for a Lambda Function:

functions:
  getProfileInfo:
    handler: profile.get
    events:
      - httpApi:
            method: GET
            path: /user/profile
            authorizer: serviceAuthorizer
  createProfileInfo:
    handler: profile.post
    events:
      - httpApi:
            method: POST
            path: /user/profile
            authorizer: serviceAuthorizer

In this case, we create an HTTP API with the httpApi event for each function. We then provide the functions with a method - GET and POST and a path they’re tied to with the API. These functions live inside the profile.js file but aren’t much to look at as they don’t actually take an action on user profiles in this example. Now let’s look at the last part of the serverless.yml file!

Setting up the Cognito Authorizer

There are a few options for setting up a Cognito Authorizer. Often, I’ll be lazy and just do this in the AWS console and copy and paste the details I need manually. But you can also use a tool like CloudFormation to create your Cognito User Pool and related resources for you. In this case, I do this inside of the serverless.yml file’s resources section. Let’s take a look!

In the latter half of my serverless.yml file you should see this:

resources:
  Resources:
    HttpApi:
      DependsOn: serviceUserPool
    serviceUserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: service-user-pool-${opt:stage, self:provider.stage}
        UsernameAttributes:
          - email
        AutoVerifiedAttributes:
          - email
    serviceUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        ClientName: service-user-pool-client-${opt:stage, self:provider.stage}
        AllowedOAuthFlows:
          - implicit
        AllowedOAuthFlowsUserPoolClient: true
        AllowedOAuthScopes:
          - phone
          - email
          - openid
          - profile
          - aws.cognito.signin.user.admin
        UserPoolId:
          Ref: serviceUserPool
        CallbackURLs: 
          - https://localhost:3000
        ExplicitAuthFlows:
          - ALLOW_USER_SRP_AUTH
          - ALLOW_REFRESH_TOKEN_AUTH
        GenerateSecret: false
        SupportedIdentityProviders: 
          - COGNITO
    serviceUserPoolDomain:
      Type: AWS::Cognito::UserPoolDomain 
      Properties:
        UserPoolId: 
          Ref: serviceUserPool
        Domain: service-user-pool-domain-${opt:stage, self:provider.stage}-${self:provider.environment.DOMAIN_SUFFIX}

This section near the top allows me to make sure that my Cognito User Pool will be created before the HTTP API so that I can use it when creating my HTTP API:

    HttpApi:
      DependsOn: serviceUserPool

After that, I create a few more AWS resources:

  • The serviceUserPool
  • The serviceUserPoolClient
  • And the serviceUserPoolDomain

Each of these will be required in order to get the details I need to register users in my application and then to allow them to get JSON Web Tokens on sign-in that they can use to authenticate to my API.

You’ll notice that there is also some syntax in there that looks like this: ${stuff-in-here}. This syntax is pulling variables from the rest of the serverless.yml file in order to create unique stage-specific AWS resources. In this case, they pull from the stage of the application or the DOMAIN_SUFFIX defined earlier in the file.

With all these things in serverless.yml explained, we can now deploy our service with serverless deploy!

This should go through the process of deploying our HTTP API service out and creating the Cognito resources we need to add authorization and authentication to it! If you’d like to learn more about how to test it out using the Cognito Hosted UI check out my post on this here!

Tomorrow, I’ll show you how to write a more complex Lambda handler using Python that actually gets and creates some profile data for each user!

Have questions or comments or just want to learn more? Join me (Fernando) in the Moonrise Labs Discord!