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.
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.
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"
}
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.
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})
Code for Dean's point. Assuming we have a Websocket constructor, using AWS Amplify on the frontend, and want to protect our websocket connection:
const signedUrl = await signUrl(
import.meta.env.VITE_WEBSOCKET_API_URL,
'execute-api', // websocket api AWS service name
import.meta.env.VITE_API_REGION,
);
const socket = new WebSocket(signedUrl);
// handle socket events below...
signUrl.js
import { Auth, Signer } from 'aws-amplify';
export async function signUrl(url, service, region) {
const essentialCredentials = Auth.essentialCredentials(
await Auth.currentCredentials(),
);
const credentials = {
secret_key: essentialCredentials.secretAccessKey,
access_key: essentialCredentials.accessKeyId,
session_token: essentialCredentials.sessionToken,
};
const serviceInfo = { region, service };
return Signer.signUrl(url, credentials, serviceInfo);
}
This will try to connect to the websocket using a signed URL.
If we try to connect with an unsigned URL, we get 403:
wscat --connect wss://<your-api-id>.execute-api.us-east-1.amazonaws.com/<your-stage>
© 2022 - 2025 — McMap. All rights reserved.