10

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.

Dean
  • 974
  • 2
  • 17
  • 28

3 Answers3

9

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"
}
Rudi Starcevic
  • 645
  • 1
  • 11
  • 17
  • Hi Rudi, in this approach, will the user be able to sign in as long as the assumed IAM is not expired? So let us say that the user signed in, and then changed the password. Will he be able to continue using the endpoint using the previously generated querystring until the generated token is expired? – Mohammed Noureldin Apr 19 '21 at 00:37
  • This probably saved my some serious time. I tried using the standard Websocket library which works fine with no auth, but fails with 403 when using signed URL. ``` new WebSocket(url); << fails BUT new W3CWebSocket(url) << Good ``` – ucipass Sep 26 '21 at 21:55
  • @ucipass I don't use bs4 but I see this error in my lambda CloudWatch log: Runtime.ImportModuleError: Unable to import module 'app': No module named 'bs4'. Did you notice this bs4 error? API Gateway AWS_IAM authorization seems to work for me even though I am using the standard WebSocket(signedUrl) – Jun Jun 24 '23 at 02:08
5

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.

Dean
  • 974
  • 2
  • 17
  • 28
0

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})
Mohammed Noureldin
  • 14,913
  • 17
  • 70
  • 99