serverless: API Gateway WebSocket aws_iam authorizer does not work

This is a Bug Report

Description

  • What went wrong? Specifying authorizer: aws_iam in a websocket event causes deployment to fail

  • What did you expect should have happened? The deployment should have created an APi Gateway websocket with the AWS_AMI authorizer type.

  • What was the config you used?

functions:
  # Client connections and disconnections from API Gateway WS
  connectionManager:
    handler: src/connection.handler
    events:
      - websocket:
          route: $connect
          authorizer: aws_iam
      - websocket:
          route: $disconnect
  • What stacktrace or error message from your provider did you see?
The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource AwsUnderscoreiamLambdaFunction

It appears to be treating aws_iam as an unknown custom authorizer Lambda function instead of treating it as a special case as in http events.

Similar or dependent issues:

Additional Data

  • Serverless Framework Version you’re using: Serverless Version 1.42.3

  • Operating System: CentOS7

  • Stack Trace:

  • Provider Error messages:

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 2
  • Comments: 16

Most upvoted comments

Friendly FYI for anyone waiting on this to be baked into the code, you can achieve the same effect with some overrides in your serverless.yml resources. I believe the websocket $connect route is always named “SconnectWebsocketsRoute” in the cloudformation, so you can do this in your serverless.yml:

resources:
  Resources:
    SconnectWebsocketsRoute:
      Type: 'AWS::ApiGatewayV2::Route'
      Properties:
        AuthorizationType: "AWS_IAM"

And achieve the equivalent result to the desired authorizer: aws_iam on the function event websocket $connect route.

Some related FYIs:

  • It is not documented in the API Gateway developer guide, but be advised that because no browsers appear to allow you to add custom headers (e.g. authorization-related headers) to the HTTP GET that opens your WebSocket connection, you must supply AWS v4 signature information as query string parameters a la S3 pre-signed URLs. At least, this worked for me 😃.
    • AWS Amplify does not yet have functionality to connect to an API Gateway WebSocket in the manner it has for REST APIs. However, you can string together some of the lower level functionality of Amplify to generate a signed wss:// URL for usage with some other JavaScript WebSocket library. For example, if you import Auth from aws-amplify, you’d then call Auth.currentCredentials() to get a promise that will return credentials object, then Auth.essentialCredentials(credentials) to get an object with a set of temporary credentials. Then you’d take your wss:// URL as a string, build an access info object from the temporary credentials and a service info object, and use them with Signer.signUrl, e.g. Signer.signUrl(wssUrl, accessInfo, serviceInfo), to get your signed wss:// URL for use with the WebSockets library of your choice (and can test this URL with wscat -c if desired). YMMV.
  • It is not possible (at present) to configure the WebSocket $connect route integration with “invoke with caller credentials” set as “checked” and set the “execution role” to arn:aws:iam::*:user/* due to an undocumented bug (despite the documentation strongly implying you can do this). I ended up raising an AWS support case to find this out. No timeline for a fix was provided by AWS support.

@rudijs, does AWS_IAM authorization work in your case? In my case I see the AWS_IAM in the websocket settings (API Gateway console) but still able to call lambda method without any authorization information. I expect to get 403 in case of the call without auth header.

@DmitryOstapuk The way it works for me is:

  • Create a websocket service using the serverless framework (sls deploy etc)

This is an example of what I currently have (I’m using typescript and adding in saving the connectionId to dynamodb)

service:
  name: serverless-ws-test
# app and org for use with dashboard.serverless.com
#app: your-app-name
#org: your-org-name

custom:
  webpack:
    webpackConfig: ./webpack.config.js
    includeModules: true

# Add the serverless-webpack plugin
plugins:
  - serverless-webpack

provider:
  name: aws
  runtime: nodejs12.x
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'ap-southeast-1'}
  websocketsApiName: custom-websockets-api-name
  websocketsApiRouteSelectionExpression: $request.body.action # custom routes are selected by the value of the action property in the body
  logs:
    websocket:
      level: ERROR
  apiGateway:
    minimumCompressionSize: 1024 # Enable gzip compression for responses > 1 KB
  environment:
    AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
    REGION: ${self:provider.region}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
        - dynamodb:DeleteItem
      # Restrict our IAM role permissions to
      # the specific table for the stage
      Resource:
        - arn:aws:dynamodb:ap-southeast-1:xxxxxxx:table/WSConnections

functions:
  connect:
    handler: handler.connect
    events:
      - websocket:
          route: $connect

  disconnect:
    handler: handler.disconnect
    events:
      - websocket:
          route: $disconnect

  hello:
    handler: handler.hello
    events:
      - websocket:
          route: hello
          routeResponseSelectionExpression: $default

resources:
  Resources:
    SconnectWebsocketsRoute:
      Type: "AWS::ApiGatewayV2::Route"
      Properties:
        AuthorizationType: "AWS_IAM"

If you try to connect to that using the wss:// endpoint you’ll get a 403 - that’s how it works out of the box.

Then in the front end, the app needs to sign in, I’m using Cognito and Amplify in a React app.

After auth is done, create a signed wss:// endpoint and the websocket client can connect OK.

What I’ve done is experimental so far, just tinkering around with it.

Hope that helps.

I have not got it working yet, but the first weird behavior I see, after editing my API and deploying it as shown above, I am still able to access the WebSocket API without any restriction. To solve this, I needed to manually Deploy the API again manually from the AWS GUI console, which is totally weird and does not make sense (and therefore it took me 2 days to figure out why I am able to access the API with no 403 error).

@rudijs It was very strange thing for me. websocket in AWS console was configured with the AWS_IAM, but wscat -c "wss://********.execute-api.us-east-2.amazonaws.com/dev worked without any signature. But when I created a right signature and repeat wscat with it the previous request without signature start working as expected with 403 error.