4
votes

I've set up an API Gateway using WebSocket protocol. On the '$connect' route request setting, I selected 'AWS_IAM' as the authorization method. The web app needs to make a connection to this WebSocket API after a user logged in via Cognito. How do I then authorize the WebSocket API request from the JavaScript on the web app? With the HTTP API Gateway, I can generate the signature from access key and session token, which got passed in to the request header. But I can't pass headers in a WebSocket request.

3

3 Answers

4
votes

This is some example/pseudo code that works for me:

Using AWS Amplify Authenticated user:

import { w3cwebsocket as W3CWebSocket } from "websocket"
import { Auth, Signer } from "aws-amplify"

let wsClient: any = null

export const client = async () => {
  if (wsClient) return wsClient

  if ((await Auth.currentUserInfo()) === null) return wsClient

  const credentials = await Auth.currentCredentials()

  const accessInfo = {
    access_key: credentials.accessKeyId,
    secret_key: credentials.secretAccessKey,
    session_token: credentials.sessionToken,
  }

  const wssUrl = "wss://YOUR-API-ID.execute-api.REGION.amazonaws.com/dev"

  const signedUrl = Signer.signUrl(wssUrl, accessInfo)

  wsClient = new W3CWebSocket(signedUrl)

  wsClient.onerror = function () {
    console.log("[client]: Connection Error")
  }

  wsClient.onopen = function () {
    console.log("[client]: WebSocket Client Connected")
  }

  wsClient.onclose = function () {
    console.log("[client]: Client Closed")
  }

  wsClient.onmessage = function (e: any) {
    if (typeof e.data === "string") {
      console.log("Received: '" + e.data + "'")
    }
  }

  return wsClient
}

Then also using AWS Cognito needs this permission:

{
  "Action": ["execute-api:Invoke"],
  "Resource": "arn:aws:execute-api:REGION:ACCOUNT-ID-OR-WILDCARD:*/*/$connect",
  "Effect": "Allow"
}
3
votes

I have got an answer from AWS support. I will need to sign the wss URL. So instead of setting request headers in a HTTP request, the signature information will be passed to the url in the query string parameters. A signed wss URL looks like: wss://API_ID.execute-api.region.amazonaws.com/dev?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ACCESSKEY/20200131/region/execute-api/aws4_request&X-Amz-Date=20200131T100233Z&X-Amz-Security-Token=SECURITY_TOKEN&X-Amz-SignedHeaders=host&X-Amz-Signature=SIGNATURE.

To generate the signed URL, I can use Signer.signUrl method from @aws-amplify/core library.

0
votes

I implemented this Dart code which signs the AWS request URLs. This is particularly helpful to connect to a IAM-secured WebSocket API Gateway.

https://github.com/MohammedNoureldin/aws-url-signer

I know that putting links in answers in discouraged, but this will not make sense to copy the whole 100 lines of code here.

The usage of my implementation will look like this:

String getSignedWebSocketUrl(
    {String apiId,
    String region,
    String stage,
    String accessKey,
    String secretKey,
    String sessionToken})