Aws-mobile-appsync-sdk-js: realtime-subscription-handshake-link: BadRequestException

Created on 4 Feb 2021  路  6Comments  路  Source: awslabs/aws-mobile-appsync-sdk-js

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
Subscription over web-socket fail for IAM authorization. Receive following error when debugging _handleIncomingSubscriptionMessage.
{ errorType: "BadRequestException", message: "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method....}
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.
Successfully initialized handshake with the same signature using IAM auth. The above error only receives at
_handleIncomingSubscriptionMessage. Probably because websocket signature is different.

What is the expected behavior?
https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html
Looks like realtime-subscription-handshake-link sign header using "body" key instead of "data" key for IAM authorization from the documentation. However, change "body" to "data" to sign header throw signature mismatch when initializing handshake.

Which versions and which environment (browser, react-native, nodejs) / OS are affected by this issue? Did this work in previous versions?

  • "react-native": "^0.64.0-rc.2"
  • "@apollo/client": "^3.4.0-beta.4"
  • "@aws-amplify/auth": "^3.4.16"
  • "aws-appsync-auth-link": "^3.0.3"
  • "aws-appsync-subscription-link": "^3.0.5"

For the moment, I revert back to use mqtt. It works fine so far, but the deprecation is coming soon. Love to be able to use web socket ASAP.

Cheers,

Most helpful comment

After a load of comparison between @successx's changes and the original code. The change that "fixed" the issue for me was to comment out this line:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-subscription-link/src/realtime-subscription-handshake-link.ts#L104

This fixes the issue because these graphql_headers seem to be used here to create the "start subscription" message:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-subscription-link/src/realtime-subscription-handshake-link.ts#L256

but is not used here to initialize the websocket connection:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-subscription-link/src/realtime-subscription-handshake-link.ts#L366

Which results in a mismatch of headers and thus a Bad Request

I believe the fix isn't to comment out the line I originally linked but to actually pass through the graphql_headers to _initializeWebSocketConnection so that the headers for initializing the websocket connection actually match the subsequent "start subscription" message.

I've made a PR here https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/633

All 6 comments

Same issue here with:

"@apollo/client": "3.3.13",
"aws-appsync-auth-link": "3.0.4",
"aws-appsync-subscription-link": "3.0.6",
"aws-sdk": "2.873.0",

AppSync no longer supports MQTT on newly created GraphQL API. Would be great to fix IAM auth for websockets!

Hi, Same issue here, I got the same error from IAM auth since I removed my old appsync instance and redeploy it.

    "@apollo/client": "^3.3.13",
    "aws-appsync-auth-link": "^3.0.4",
    "aws-appsync-subscription-link": "^3.0.6",

I need to generate my graphql client depending on the authentication type (IAM / Cognito) to let users log from Federated Entites like Google, either by username/pass. AWSAppsyncClient doesn't let me create 2 instances because of conflict issues. So I chosed ApolloClient.

const createApolloClient = authType => {

    const httpLink = new HttpLink({
        uri: APIConfiguration.aws_appsync_graphqlEndpoint,
    });

    const cognitoAuth = {
        type: APIConfiguration.aws_appsync_authenticationTypeCognito,
        jwtToken: APIConfiguration.aws_jwToken
    };
    const iamAuth = {
        type: APIConfiguration.aws_appsync_authenticationTypeIAM,
        credentials: () => Auth.currentCredentials()
    };

    return new ApolloClient({
        cache: new InMemoryCache(),
        defaultOptions: {
            watchQuery: { fetchPolicy: 'no-cache', errorPolicy: 'ignore' },
            query: { fetchPolicy: 'no-cache', errorPolicy: 'all' }
        },
        connectToDevTools: process.env.APP_STAGE === 'dev',
        link: from([
            createAuthLink({
                url: APIConfiguration.aws_appsync_graphqlEndpoint,
                region: APIConfiguration.aws_appsync_region,
                auth: authType === 'COGNITO'
                    ? cognitoAuth
                    : iamAuth
            }),
            split(op => {
                const { operation } = op.query.definitions[0];

                if (operation === 'subscription') {
                    return false;
                }

                return true;
            }, httpLink, createSubscriptionHandshakeLink(
                {
                    url: APIConfiguration.aws_appsync_graphqlEndpoint,
                    region: APIConfiguration.aws_appsync_region,
                    auth: authType === 'COGNITO'
                        ? cognitoAuth
                        : iamAuth
                },
                httpLink
            ))
        ]),
    });
}

with

export const APIConfiguration = {
    aws_appsync_graphqlEndpoint: process.env.GRAPHQL_ENDPOINT,
    aws_appsync_region: process.env.AWS_REGION,
    aws_appsync_authenticationTypeIAM: "AWS_IAM",
    aws_appsync_authenticationTypeCognito: "AMAZON_COGNITO_USER_POOLS",
    aws_jwToken: async () => {
        const session = await Amplify.Auth.currentSession();
        return session.getIdToken().getJwtToken();
    },
};

@successx how did you revert back to mqtt ? I'm very intrigued ... As I use Serverless config file to deploy, it deployed a new appsync instance, I can't find where I can set the protocol to use for subscriptions (pure websocket / mqtt over websocket)

Edit : When I log with username/password, I got a Connection closed error from my subscription with no clue

Hm, I didn't realise app sync already deprecate mqtt. For us, simply use _createSubscriptionHandshakeLink(url)_ does the trick for mqtt. Not sure if it works for your auth cases.
For anyone waiting for the fix from aws, I've forked a stripped down version with IAM auth over websocket subscription only (link). We've been using it for the web for a while in our monorepo. Feel free to add your own logging solution to improve it.

@successx How did you fix it?

I've read diff -ru aws-mobile-appsync-sdk-js/packages/aws-appsync-subscription-link aws-appsync-subscription-link but as there are lots of changes, I did not understand what's the fix there.

After a load of comparison between @successx's changes and the original code. The change that "fixed" the issue for me was to comment out this line:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-subscription-link/src/realtime-subscription-handshake-link.ts#L104

This fixes the issue because these graphql_headers seem to be used here to create the "start subscription" message:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-subscription-link/src/realtime-subscription-handshake-link.ts#L256

but is not used here to initialize the websocket connection:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-subscription-link/src/realtime-subscription-handshake-link.ts#L366

Which results in a mismatch of headers and thus a Bad Request

I believe the fix isn't to comment out the line I originally linked but to actually pass through the graphql_headers to _initializeWebSocketConnection so that the headers for initializing the websocket connection actually match the subsequent "start subscription" message.

I've made a PR here https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/633

I've been having the same issue whereby the signed headers appear to mismatch those included in the GraphQL message payload.
As @Eveliyse 's PR (#633) indicates, the removal of graphql_headers seems to resolve the issue in question.
Thanks for the investigation!

Edit:
This change appears to remove any custom headers which are set in a prior link.
So if no custom headers are required, the above fix works. But care should be taken when your connection requires custom headers also be set.

import { setContext } from "@apollo/link-context";

const linkParams = {
  url: "",
  region: "",
  auth: {
    type: AUTH_TYPE.AWS_IAM,
    credentials: {}, // IAM credentials
  } as AuthOptions,
};

const httpAndWSCompositeLink = ApolloLink.from([
  setContext((request, previousContext) => ({
    headers: {
      ...previousContext.headers,
      "x-my-header": "foo-bar",
    },
  })),
  createAuthLink(linkParams),
  createSubscriptionHandshakeLink(linkParams),
]);
Was this page helpful?
0 / 5 - 0 ratings