httpApi and JWT configuration not transforming properly?

I am putting together a Python 3.8-based Serverless sample application, and wanted to experiment with a JWT httpApi sample client. Running into errors with what gets transformed. Here is the snippet of serverless.yml:

provider:
  name: aws
  runtime: python3.8
  stage: dev
  region: us-east-1
  environment:
    CONFIG_TABLE: ${self:custom.tableNames.sharedTasks}
    CONFIG_DYNAMODB_ENDPOINT: ${self:custom.endpoints.dynamodb-url}
  httpApi:
    authorizers:
      sharedTasksJwtAuthorizer:
        identitySource: $request.header.Authorization
        jwtConfiguration:
          issuerUrl:
            Fn::Join:
              - ''
              -
                - "https://cognito-idp."
                - ${self:custom.region}
                - ".amazonaws.com/"
                - Ref: CognitoIdentityPool
          audience:
           - Ref: CognitoUserClientPoolId

I’ve tried issuer, issuerUrl. Neither transform - the sls print shows the values in the transformed serverless config, but in .serverless the update cloudformation shows the audience as null, and does not send off issuer or issuerUrl.

Am I doing something wrong, or is the httpApi support either different than the post announcing it or has bugs? It would be great to see a Jwt sample with a Cognito User and Identity Pool with the httpApi like you have with auth0 to base things on.

Thanks for any community help here.

Best,

Ken

1 Like

Here is the sls print -s dev output

httpApi:
    authorizers:
      sharedTasksJwtAuthorizer:
        identitySource: $request.header.Authorization
        jwtConfiguration:
          issuerUrl:
            'Fn::Join':
              - ''
              - - 'https://cognito-idp.'
                - us-east-1
                - .amazonaws.com/
                - Ref: CognitoIdentityPool
          audience:
            - Ref: CognitoUserClientPoolId

Here is the cloudformation update file content:

"HttpApi": {
  "Type": "AWS::ApiGatewayV2::Api",
  "Properties": {
    "Name": "dev-krimple-shared-tasks",
    "ProtocolType": "HTTP"
  }
},
"HttpApiStage": {
  "Type": "AWS::ApiGatewayV2::Stage",
  "Properties": {
    "ApiId": {
      "Ref": "HttpApi"
    },
    "StageName": "$default",
    "AutoDeploy": true
  }
},
"HttpApiAuthorizerSharedTasksJwtAuthorizer": {
  "Type": "AWS::ApiGatewayV2::Authorizer",
  "Properties": {
    "ApiId": {
      "Ref": "HttpApi"
    },
    "AuthorizerType": "JWT",
    "IdentitySource": [
      "$request.header.Authorization"
    ],
    "JwtConfiguration": {
      "Audience": [
        null
      ]
    },
    "Name": "sharedTasksJwtAuthorizer"
  }
},

The error is:

Error: The CloudFormation template is invalid: [/Resources/HttpApiAuthorizerSharedTasksJwtAuthorizer/Type/JwtConfiguration/Audience/0] 'null' values are not allowed in templates
  at /Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/serverless/lib/plugins/aws/deploy/lib/validateTemplate.js:20:13
  at tryCatcher (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/util.js:16:23)
  at Promise._settlePromiseFromHandler (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/promise.js:547:31)
  at Promise._settlePromise (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/promise.js:604:18)
  at Promise._settlePromise0 (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/promise.js:649:10)
  at Promise._settlePromises (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/promise.js:725:18)
  at _drainQueueStep (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/async.js:93:12)
  at _drainQueue (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/async.js:86:9)
  at Async._drainQueues (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/async.js:102:5)
  at Immediate.Async.drainQueues [as _onImmediate] (/Users/kenrimple/projects/serverless-projects/shared-tasks/node_modules/bluebird/js/release/async.js:15:14)
  at processImmediate (internal/timers.js:456:21)
  at process.topLevelDomainCallback (domain.js:137:15)

  For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.

Maybe something pebkac-y here by myself.

Version:

$ serverless --version
Framework Core: 1.67.0
Plugin: 3.6.1
SDK: 2.3.0
Components: 2.22.3

And the plot thickens. Based on reviewing this transform code in the serverless plugin for aws in the events section (httpapi/index.js):

compileAuthorizers() {
  for (const authorizer of this.config.authorizers.values()) {
    this.cfTemplate.Resources[
      this.provider.naming.getHttpApiAuthorizerLogicalId(authorizer.name)
    ] = {
      Type: 'AWS::ApiGatewayV2::Authorizer',
      Properties: {
        ApiId: this.getApiIdConfig(),
        AuthorizerType: 'JWT',
        IdentitySource: [authorizer.identitySource],
        JwtConfiguration: {
          Audience: Array.from(authorizer.audience),
          Issuer: authorizer.issuerUrl,
        },
        Name: authorizer.name,
      },
    };
  }
}

it appears that the settings should be indented like this:

httpApi:
  authorizers:
    sharedTasksJwtAuthorizer:
      identitySource: $request.header.Authorization
      issuerUrl:
        Fn::Join:
          - ''
          - - "https://cognito-idp."
            - ${self:custom.region}
            - ".amazonaws.com/"
            - CognitoIdentityPool
      audience:
        - CognitoUserClientPoolId

which gives the cloudformation of

"HttpApiAuthorizerSharedTasksJwtAuthorizer": {
  "Type": "AWS::ApiGatewayV2::Authorizer",
  "Properties": {
    "ApiId": {
      "Ref": "HttpApi"
    },
    "AuthorizerType": "JWT",
    "IdentitySource": [
      "$request.header.Authorization"
    ],
    "JwtConfiguration": {
      "Audience": [
        "CognitoUserClientPoolId"
      ],
      "Issuer": {
        "Fn::Join": [
          "",
          [
            "https://cognito-idp.",
            "us-east-1",
            ".amazonaws.com/",
            "CognitoIdentityPool"
          ]
        ]
      }
    },
    "Name": "sharedTasksJwtAuthorizer"
  }
},

Good, now I get:

An error occurred: HttpApiAuthorizerSharedTasksJwtAuthorizer - Caught exception
when connecting to https://cognito-idp.us-east-1.amazonaws.com/CognitoIdentityPool/.well-known/openid-configuration for issuer 
https://cognito-idp.us-east-1.amazonaws.com/CognitoIdentityPool. Please try again later. 
Error: Invalid issuer: https://cognito-idp.us-east-1.amazonaws.com/CognitoIdentityPool. 
Issuer must have a valid discovery endpoint ended with '/.well-known/openid-configuration' 
(Service: AmazonApiGatewayV2; Status Code: 400; Error Code: BadRequestException; 
Request ID: daa73cc8-be27-4bec-a3cf-a1cc0d6f9d9f).

So it appears I need more Cognito configuration to enable the well-known end point and this is where my JWT knowledge is very weak. I’ll dig around locally but if anyone has gotten past this point with Cognito and httpApi and deploying let me know. I’m sure it’s out of the Serverless realm now and into API Gateway V2 and Cognito integration config.