amplify-js: v6 - ID Token as Authorization header not working with DataStore/App Sync any more (was working with v5, solution proposed)

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

GraphQL API

Amplify Categories

api

Environment information

# Put output below this line
  System:
    OS: macOS 14.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 1.36 GB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.18.2 - /opt/homebrew/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 9.8.1 - /opt/homebrew/bin/npm
    pnpm: 8.10.5 - /opt/homebrew/bin/pnpm
    Watchman: 2023.11.13.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 119.0.6045.159
    Safari: 17.1
  npmGlobalPackages:
    corepack: 0.19.0
    npm: 9.8.1

Describe the bug

I have some Custom Claims in my ID Token, generated through a Pre Token Generation Lambda Trigger. I need to pass those to API GraphQL. It was working well in V5 but now does not anymore for Subscriptions in V6. I also have the solution, so please read it at the end of my bug description!

In V5 I was doing it like this:

// V5
import { Amplify } from "aws-amplify";
import config from "./aws-exports";

Amplify.configure({
  ...config,
  API: {
    graphql_headers: async () => ({
      // Replace Access Token by Identity Token
      Authorization: (await Auth.currentSession()).getIdToken().getJwtToken(),
    }),
  },
});

There was no Documentation how to do it in V6, so I figured it out (please also find my request to update the docs aws-amplify/docs#6513 )

So in V6 it is working like this:

// V6
import { Amplify } from "aws-amplify";
import { fetchAuthSession } from "aws-amplify/auth";
import config from "./amplifyconfiguration.json";

Amplify.configure(config, {
  API: {
    GraphQL: {
      headers: async () => ({
        Authorization: (
          await fetchAuthSession()
        ).tokens?.idToken?.toString() as string,
      }),
    },
  },
});

Now here is my point regarding the bug:

  • DataStore does work for sync, queries and mutations
  • It does NOT work for subscriptions!

** Cause for the bug **

  • The customized GraphQL headers set through the Amplify.configure options as shown above are not passed to the AWSAppSyncRealTimeProvider

** Temporary Resolution / Hotfix for the bug **

In order to make it work BOTH for React Native and the Web I had to patch those three files:

  • node_modules/@aws-amplify/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts
  • node_modules/@aws-amplify/api-graphql/dist/esm/Providers/AWSAppSyncRealTimeProvider/index.mjs
  • node_modules/@aws-amplify/api-graphql/dist/cjs/Providers/AWSAppSyncRealTimeProvider/index.js
	private async _awsAuthTokenHeader({ host }: AWSAppSyncRealTimeAuthInput) {
		const session = await fetchAuthSession();

		return {
			Authorization: session?.tokens?.idToken?.toString(), // <-- Change accessToken for idTolken
			host,
		};
	}

** Change request **

  • Pass the customized GraphQL headers to _awsAuthTokenHeader in AWSAppSyncRealTimeProvider. There is already a property graphql_headers in Type AWSAppSyncRealTimeAuthInput but I think it is not correctly passed down all the way. There also is another variable additionalCustomHeaders, I think this is the place where the Option API: {} is fetched from the Amplify.configure singleton.

I hope my description was understandable. Thanks for implementing this soon!

Expected behavior

Passing ID Token to GraphQL Header should work in V6 for Queries, Mutations AND Subscriptions as it worked in V5.

Reproduction steps

  • Create a GraphQL schema with an Auth rule based on a custom claim
  • Generate custom claim through a Pre Token Generation Lambda Trigger
  • Configure Amplify to use idToken as Authorization header
// V6
import { Amplify } from "aws-amplify";
import { fetchAuthSession } from "aws-amplify/auth";
import config from "./amplifyconfiguration.json";

Amplify.configure(config, {
  API: {
    GraphQL: {
      headers: async () => ({
        Authorization: (
          await fetchAuthSession()
        ).tokens?.idToken?.toString() as string,
      }),
    },
  },
});

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

About this issue

  • Original URL
  • State: closed
  • Created 7 months ago
  • Reactions: 2
  • Comments: 29 (11 by maintainers)

Commits related to this issue

Most upvoted comments

@jgo80, thank you for creating this issue. I’ve marked this as a bug and there’s a fix coming soon. I’ll update this issue once it’s ready and released.

I am trying to configure Datastore with custom Authentication using AWS_LAMBDA but it’s constantly showing unauthorized but on the other hand same auth flow is working fine with App Sync. Need Help!

DataStore.configure({ authProviders: { functionAuthProvider: async () => { const authToken = await refreshAuthToken(); return { token: authToken }; } } });

@david-mcafee Talking about DataStore vs. GraphQL API - Both actually use the same library AWSAppSyncRealTimeProvider. I both use DataStore and GraphQL API. I can confirm idToken override works with GraphQL API regarding queries and mutations - however I did not check Subscriptions with GraphQL API. I think the problem is originating from AWSAppSyncRealTimeProvider for both DataStore and GraphQL API.

Yep, you’re correct that they both use the AWSAppSyncRealTimeProvider (and sounds like you may know already, but DataStore also uses the GraphQL API category). To clarify, I was asking since it was unclear to me which category you were using, and I wanted to make sure I was reproducing your specific use-case! 😃

I was able to reproduce the problem, and I’m currently working on the fix + tests. Will update this ticket when that is ready.

Hi @david-mcafee , sure I tried with the unstable version today and it works fine with subscriptions in my case too.

@david-mcafee thx for your infos - unfortunately the fix seems not to be deployed with the current unstable?

yarn add aws-amplify@unstable results in "aws-amplify": "^6.0.6-unstable.1c5010c.0" which results in "@aws-amplify/api-graphql": "4.0.6-unstable.f2b7a8d.0+f2b7a8d"

Maybe your fix did not make it to the unstable pre-release?

Okay, my bad, I now can confirm it is working with the unstable pre-release @david-mcafee

@hanna-becker wait, there are different things getting mixed up.

  1. Default Authorization Token is the accessToken, not the idToken. So the accessToken has all default Cognito attributes.
  2. I was not talking about group claims (groups in Amazon Cognito user pools), but I was talking about custom claims I create with a Pre Token Generation Lambda Trigger > Those claims only are contained in the idToken

So my mentioned code is the official way to override/pass the idToken to the Authorization header. However the override was not correctly implemented for subscriptions, that’s all. Just a missing link. I just want to avoid confusion and mixing up facts.

Im having the same issue my authorization mode of model is custom lambda and Datastore is using authProvider property to add authorization token for graphql call… here is my code it was working in v5 but not in v6 after migration

DataStore.configure({ syncPageSize: 1000, maxRecordsToSync: 200000, authProviders: { functionAuthProvider: async () => { console.log("refreshAuthToken",userData.token) const authToken = await refreshAuthToken(); // refreshAuthToken return { token: userData.token }; } }, syncExpressions: [ syncExpression(GlobalLookups, () => { try{ return Predicates.ALL; }catch(error){ console.log(error, userData); } }), syncExpression(CatHerd, () => { try{ if(userType.carrier.includes(userData.company_type)){ return (c) => c.carrier_id.eq(userData.company_id); }else{ return (c) => c.dd_tenant.eq(TENANT#${userData.company_id}); } }catch(error){ console.log(error); } }) ], errorHandler: (error) => { console.log(error) }, });

@OperationalFallacy - can you elaborate more on your use case - ideally with code snippets, if at all possible? Are you saying that headers passed to generateClient are not being included in queries?

Additionally, this change has now been officially released with the latest version of Amplify.

@david-mcafee the issue is about DataStore, which is based on AppSync, so the answer is both. I just pointed out where the fix is to be inserted, so there should not be many questions.

@nadetastic thx, I opened both issues and also referenced them 😉 But aws-amplify/docs#6513 only faces the lag of documentation. This issue deals with a bug!