Before opening, please confirm:
JavaScript Framework
React
Amplify APIs
GraphQL API
Amplify Version
v6
Amplify Categories
Not applicable
Backend
CDK
Environment information
Details
# Put output below this line
System:
OS: Linux 6.6 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
CPU: (14) x64 Intel(R) Core(TM) Ultra 5 135U
Memory: 2.09 GB / 7.49 GB
Container: Yes
Shell: 5.2.21 - /bin/bash
Binaries:
Node: 22.22.1 - /home/<redacted>/.nvm/versions/node/v22.22.1/bin/node
npm: 11.12.0 - /home/<redacted>/.nvm/versions/node/v22.22.1/bin/npm
npmPackages:
%name%: 0.1.0
@azure/msal-browser: ^4.24.0 => 4.24.0
@azure/msal-react: ^3.0.20 => 3.0.20
@eslint/js: 9.39.4 => 9.39.4
@playwright/test: ^1.56.1 => 1.56.1
@testing-library/jest-dom: 6.6.3 => 6.6.3
@testing-library/react: 16.3.0 => 16.3.0
@testing-library/user-event: 14.6.1 => 14.6.1
@types/aws-lambda: 8.10.147 => 8.10.147
@types/bonjour: 3.5.13 => 3.5.13
@types/node: 22.15.17 => 22.15.17
@types/react: 19.1.0 => 19.1.0
@types/react-dom: 19.1.0 => 19.1.0
@types/testing-library__jest-dom: 5.14.9 => 5.14.9
@types/trusted-types: 2.0.7 => 2.0.7
@types/uuid: 10.0.0 => 10.0.0 (9.0.8)
@vitejs/plugin-react: 4.4.1 => 4.4.1
@vitest/coverage-v8: 3.1.3 => 3.1.3
@vitest/eslint-plugin: 1.6.15 => 1.6.15
@vitest/ui: 3.1.3 => 3.1.3
aws-amplify: 6.17.0 => 6.17.0
aws-amplify/adapter-core: undefined ()
aws-amplify/adapter-core/internals: undefined ()
aws-amplify/analytics: undefined ()
aws-amplify/analytics/kinesis: undefined ()
aws-amplify/analytics/kinesis-firehose: undefined ()
aws-amplify/analytics/personalize: undefined ()
aws-amplify/analytics/pinpoint: undefined ()
aws-amplify/api: undefined ()
aws-amplify/api/internals: undefined ()
aws-amplify/api/server: undefined ()
aws-amplify/auth: undefined ()
aws-amplify/auth/cognito: undefined ()
aws-amplify/auth/cognito/server: undefined ()
aws-amplify/auth/enable-oauth-listener: undefined ()
aws-amplify/auth/server: undefined ()
aws-amplify/data: undefined ()
aws-amplify/data/server: undefined ()
aws-amplify/datastore: undefined ()
aws-amplify/in-app-messaging: undefined ()
aws-amplify/in-app-messaging/pinpoint: undefined ()
aws-amplify/push-notifications: undefined ()
aws-amplify/push-notifications/pinpoint: undefined ()
aws-amplify/storage: undefined ()
aws-amplify/storage/s3: undefined ()
aws-amplify/storage/s3/server: undefined ()
aws-amplify/storage/server: undefined ()
aws-amplify/utils: undefined ()
aws-cdk: 2.1119.0 => 2.1119.0
aws-cdk-lib: 2.250.0 => 2.250.0
aws-rum-web: ^2.1.0 => 2.1.0
axios: ^1.12.2 => 1.15.2
codemods: 0.0.0
constructs: 10.6.0 => 10.6.0
dotenv-cli: 8.0.0 => 8.0.0
esbuild: 0.25.4 => 0.25.4
eslint: 9.39.4 => 9.39.4
eslint-config-prettier: 10.1.8 => 10.1.8
eslint-plugin-react: 7.37.5 => 7.37.5
eslint-plugin-react-hooks: 7.0.1 => 7.0.1
globals: 16.4.0 => 16.4.0 (11.12.0, 14.0.0)
i18next: 25.1.2 => 25.1.2
interweave: 13.1.1 => 13.1.1
jiti: ^2.6.1 => 2.6.1
jsdom: 26.1.0 => 26.1.0
phrase-cli: 2.5.1-alpha1 => 2.5.1-alpha1
postcss-scss: 4.0.9 => 4.0.9
prettier: 3.5.1 => 3.5.1
prettier-plugin-sort-json: 4.1.1 => 4.1.1
react: 19.1.0 => 19.1.0
react-dom: 19.1.0 => 19.1.0
react-i18next: 15.5.1 => 15.5.1
react-router-dom: 6.30.3 => 6.30.3
sass: 1.88.0 => 1.88.0
sass-loader: 16.0.5 => 16.0.5
style-loader: 4.0.0 => 4.0.0
stylelint: 16.15.0 => 16.15.0
stylelint-declaration-strict-value: 1.10.11 => 1.10.11
stylelint-order: 6.0.4 => 6.0.4
stylelint-scss: 6.11.1 => 6.11.1
tsx: 4.19.4 => 4.19.4
typescript: 5.8.3 => 5.8.3
typescript-eslint: 8.58.1 => 8.58.1
uuid: 14.0.0 => 14.0.0 (11.1.0, 9.0.1)
vite: ^6.3.6 => 6.4.2
vite-plugin-svgr: 4.3.0 => 4.3.0
vite-tsconfig-paths: 5.1.4 => 5.1.4
vitest: 3.1.3 => 3.1.3
npmGlobalPackages:
aws-cdk: 2.1112.0
corepack: 0.34.6
npm-check-updates: 20.0.1
npm: 11.12.0
Describe the bug
When using client.graphql(...).subscribe(...) with AppSync endpoint using OIDC authorization mode (backed by Entra ID, using MSAL.js for client side auth) we get an UnauthorizedException (via the error handler of the subscribe() call).
This issue only affects subscriptions (when using subscribe()). This is not an issue when doing mutations or queries via the client.graphql() API, so the problem is only with subscribe() combined with OIDC.
This seems to be because Amplify JS uses the wrong token for authorization of the WebSocket connection when using subscribe(). We have setup a custom tokenProvider according to documentation. In this case Amplify always uses the accessToken from the tokenProvider (with auth mode oidc) when it should use the idToken (to satisfy the AppSync endpoint OIDC auth).
Context
- We use
aws-amplify (currently version 6.17.0) for connecting to an existing AWS AppSync GraphQL API (in a React application).
- Our project is not an initialized Amplify project, so we only use the Amplify JS library with manual configuration.
- The AppSync backend is not deployed via Amplify (but instead via Python based CDK code).
- We do not use the DataStore API.
- We only use the GraphQL API as documented in the Amplify Gen 1 docs here:
What we have tried
- We have tried explicitly overriding the auth token by passing the id token manually to the
graphql() call options (as authToken) preceding the subscribe() call. This did not help.
- We have tried setting the id token as an
Authorization header via Amplify GraphQL custom headers configuration for overriding the auth token, but that also does not affect what is used as the auth token for .subscribe() with OIDC.
- Modifying our custom tokenProvider so that it returns only the id token like this:
return { accessToken: decodeJWT(idTokenString), idToken: decodeJWT(idTokenString), }
- This is the only workaround that seems to work, but this is now semantically incorrect as the
tokenProvider is supposed to return also the accessToken (though I'm not sure if the access token is ever actually needed in this context)
Related Amplify source code references
I tried checking the logic in Amplify JS source code to verify if there is any functional way to override the auth token used in this case and from what I can see there is currently no other way to do this except using a custom tokenProvider that always returns the idToken even when the code asks for the accessToken (not sure if this can cause issues in some case where accessToken would really be needed).
I basically checked the code paths starting from the client's (GraphQLAPIClass) .graphql() call which starts here
Then browsing the code more I found this part packages/datastore/src/sync/processors/subscription.ts#L284 which I think is the related section which is used when you use the DataStore API and as you can see here it does seem to use the correct token (idToken) so probably this issue does not affect usage of DataStore API. But I did not dig very deep in DataStore and I have not tried the DataStore API myself.
Related issues
Expected behavior
When using client.graphql(...).subscribe(...) with AppSync endpoint using OIDC authorization mode, the connection should be authenticated without errors when there is a tokenProvider configured according to documentation example.
Reproduction steps
- Setup an AppSync GraphQL API that has at least one Subscription method setup using
@aws_oidc authorization mode (using Entra ID as OIDC provider for example).
- Install
aws-amplify v6 (npm package)
- Try to use Amplify JS to connect to the GraphQL endpoint using the subscribe() method (see code snippet below)
- See authentication error in browser dev tools
Code Snippet
// Put your code below this line.
import { generateClient } from "aws-amplify/api";
import { onStreamChunkTyped } from "./graphql/subscriptions";
const client = generateClient({
authMode: "oidc",
});
const sub = client
.graphql({
query: onStreamChunkTyped,
variables: { chatId },
})
.subscribe({
next: (value) => console.log("Received chunk:", value),
error: (error) => {
console.error("Subscription error:", error);
console.error(`GraphQL subscription error: ${error.errors[0].message}`);
throw new Error(`GraphQL subscription error`, { cause: error });
},
});
Log output
Details
// Put your logs below this line
[DEBUG] 04:08.462 AWSAppSyncRealTimeProvider - subscription message from AWS AppSyncRealTime: {"payload":{"errors":[{"errorType":"UnauthorizedException","message":"Valid authorization header not provided.","errorCode":401}]},"type":"connection_error"}
ConsoleLogger.ts:95 [DEBUG] 04:08.463 retryUtil - error on bound NonRetryableError: UnauthorizedException
at AWSWebSocketProvider._establishConnection (index.ts:43:27)
at async retry.ts:39:25
ConsoleLogger.ts:95 [DEBUG] 04:08.463 retryUtil - bound non retryable error NonRetryableError: UnauthorizedException
at AWSWebSocketProvider._establishConnection (index.ts:43:27)
at async retry.ts:39:25
ConsoleLogger.ts:95 [DEBUG] 04:08.463 AWSAppSyncRealTimeProvider - Connection exited with NonRetryableError: UnauthorizedException
at AWSWebSocketProvider._establishConnection (index.ts:43:27)
at async retry.ts:39:25
ConsoleLogger.ts:87 [DEBUG] 04:08.464 AWSAppSyncRealTimeProvider {err: NonRetryableError: UnauthorizedException
at AWSWebSocketProvider._establishConnection (http://l…}err: NonRetryableError: UnauthorizedException
at AWSWebSocketProvider._establishConnection (http://localhost:3000/node_modules/.vite/deps/aws-amplify_api.js?v=2553c1e0:8015:17)
at async http://localhost:3000/node_modules/.vite/deps/chunk-VNKONMSS.js?v=2553c1e0:1086:17nonRetryable: truemessage: "UnauthorizedException"stack: "Error: UnauthorizedException\n at AWSWebSocketProvider._establishConnection (http://localhost:3000/node_modules/.vite/deps/aws-amplify_api.js?v=2553c1e0:8015:17)\n at async http://localhost:3000/node_modules/.vite/deps/chunk-VNKONMSS.js?v=2553c1e0:1086:17"[[Prototype]]: Error[[Prototype]]: Object
installHook.js:1 ❌ Subscription error: {errors: Array(1)}errors: Array(1)0: message: "Connection failed: UnauthorizedException"[[Prototype]]: Objectlength: 1[[Prototype]]: Array(0)[[Prototype]]: Object
overrideMethod @ installHook.js:1
(anonymous) @ MyComponent.tsx:541
(anonymous) @ Subscriber.ts:166
(anonymous) @ Subscriber.ts:118
(anonymous) @ Subscriber.ts:86
(anonymous) @ OperatorSubscriber.ts:82
(anonymous) @ Subscriber.ts:86
_logStartSubscriptionError @ index.ts:317
_startSubscriptionWithAWSAppSyncRealTime @ index.ts:284
await in _startSubscriptionWithAWSAppSyncRealTime
(anonymous) @ index.ts:106
(anonymous) @ index.ts:127
(anonymous) @ Observable.ts:235
(anonymous) @ Observable.ts:225
errorContext @ errorContext.ts:29
(anonymous) @ Observable.ts:211
(anonymous) @ catchError.ts:113
(anonymous) @ lift.ts:24
(anonymous) @ Observable.ts:217
errorContext @ errorContext.ts:29
(anonymous) @ Observable.ts:211
(anonymous) @ MyComponent.tsx:538
(anonymous) @ MyComponent.tsx:547
(anonymous) @ react-dom-client.development.js:23949
runWithFiberInDEV @ react-dom-client.development.js:1519
commitHookEffectListMount @ react-dom-client.development.js:11905
commitHookPassiveMountEffects @ react-dom-client.development.js:12026
commitPassiveMountOnFiber @ react-dom-client.development.js:13841
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13853
flushPassiveEffects @ react-dom-client.development.js:15737
(anonymous) @ react-dom-client.development.js:15379
performWorkUntilDeadline @ scheduler.development.js:45
<MyComponent>
(anonymous) @ react-jsx-dev-runtime.development.js:346
(anonymous) @ MyComponent2.tsx:20
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<MyComponent2>
(anonymous) @ react-jsx-dev-runtime.development.js:346
AppAuthLayer @ App.tsx:62
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<AppAuthLayer>
(anonymous) @ react-jsx-dev-runtime.development.js:346
AppRoute @ App.tsx:83
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<AppRoute>
(anonymous) @ react-jsx-dev-runtime.development.js:346
(anonymous) @ App.tsx:119
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<App>
(anonymous) @ react-jsx-dev-runtime.development.js:346
(anonymous) @ main.tsx:72
Promise.then
(anonymous) @ main.tsx:66
ErrorContext.tsx:100 [App] Raw error received: GraphQL subscription error: Connection failed: UnauthorizedException
installHook.js:1 [App Error] GraphQL subscription error: Connection failed: UnauthorizedException
overrideMethod @ installHook.js:1
(anonymous) @ ErrorContext.tsx:107
(anonymous) @ MyComponent.tsx:542
(anonymous) @ Subscriber.ts:166
(anonymous) @ Subscriber.ts:118
(anonymous) @ Subscriber.ts:86
(anonymous) @ OperatorSubscriber.ts:82
(anonymous) @ Subscriber.ts:86
_logStartSubscriptionError @ index.ts:317
_startSubscriptionWithAWSAppSyncRealTime @ index.ts:284
await in _startSubscriptionWithAWSAppSyncRealTime
(anonymous) @ index.ts:106
(anonymous) @ index.ts:127
(anonymous) @ Observable.ts:235
(anonymous) @ Observable.ts:225
errorContext @ errorContext.ts:29
(anonymous) @ Observable.ts:211
(anonymous) @ catchError.ts:113
(anonymous) @ lift.ts:24
(anonymous) @ Observable.ts:217
errorContext @ errorContext.ts:29
(anonymous) @ Observable.ts:211
(anonymous) @ MyComponent.tsx:538
(anonymous) @ MyComponent.tsx:547
(anonymous) @ react-dom-client.development.js:23949
runWithFiberInDEV @ react-dom-client.development.js:1519
commitHookEffectListMount @ react-dom-client.development.js:11905
commitHookPassiveMountEffects @ react-dom-client.development.js:12026
commitPassiveMountOnFiber @ react-dom-client.development.js:13841
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13834
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13957
recursivelyTraversePassiveMountEffects @ react-dom-client.development.js:13815
commitPassiveMountOnFiber @ react-dom-client.development.js:13853
flushPassiveEffects @ react-dom-client.development.js:15737
(anonymous) @ react-dom-client.development.js:15379
performWorkUntilDeadline @ scheduler.development.js:45
<MyComponent>
(anonymous) @ react-jsx-dev-runtime.development.js:346
(anonymous) @ MyComponent2.tsx:20
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<MyComponent2>
(anonymous) @ react-jsx-dev-runtime.development.js:346
AppAuthLayer @ App.tsx:62
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<AppAuthLayer>
(anonymous) @ react-jsx-dev-runtime.development.js:346
AppRoute @ App.tsx:83
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<AppRoute>
(anonymous) @ react-jsx-dev-runtime.development.js:346
(anonymous) @ App.tsx:119
(anonymous) @ react-dom-client.development.js:23863
renderWithHooksAgain @ react-dom-client.development.js:5629
renderWithHooks @ react-dom-client.development.js:5541
updateFunctionComponent @ react-dom-client.development.js:8897
beginWork @ react-dom-client.development.js:10522
runWithFiberInDEV @ react-dom-client.development.js:1519
performUnitOfWork @ react-dom-client.development.js:15132
workLoopSync @ react-dom-client.development.js:14956
renderRootSync @ react-dom-client.development.js:14936
performWorkOnRoot @ react-dom-client.development.js:14419
performWorkOnRootViaSchedulerTask @ react-dom-client.development.js:16216
performWorkUntilDeadline @ scheduler.development.js:45
<App>
(anonymous) @ react-jsx-dev-runtime.development.js:346
(anonymous) @ main.tsx:72
Promise.then
(anonymous) @ main.tsx:66
ConsoleLogger.ts:83 [DEBUG] 04:08.468 AWSAppSyncRealTimeProvider - Error while unsubscribing undefined
ConsoleLogger.ts:83 [DEBUG] 04:08.652 AWSAppSyncRealTimeProvider - WebSocket closed
aws-exports.js
No response
Manual configuration
const myTokenProvider: TokenProvider = {
async getTokens({ forceRefresh } = {}) {
const response = await msalInstance.acquireTokenSilent({
scopes: msalAuthScopes,
account: currentAccount,
forceRefresh
})
return {
accessToken: decodeJWT(response.accessToken!),
/**
* NOTE: Amplify `client.graphql(...).subscribe(...)` only works if the previous line is changed to:
* accessToken: decodeJWT(response.idToken!) // <- use idToken instead of accessToken
*/
idToken: decodeJWT(response.idToken!)
}
},
};
Amplify.configure(
{
API: {
GraphQL: {
endpoint: import.meta.env.APPSYNC_API_URL,
region: import.meta.env.AWS_REGION,
defaultAuthMode: "oidc",
},
},
},
{
Auth: {
tokenProvider: myTokenProvider,
},
},
);
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
Before opening, please confirm:
JavaScript Framework
React
Amplify APIs
GraphQL API
Amplify Version
v6
Amplify Categories
Not applicable
Backend
CDK
Environment information
Details
Describe the bug
When using
client.graphql(...).subscribe(...)with AppSync endpoint using OIDC authorization mode (backed by Entra ID, using MSAL.js for client side auth) we get anUnauthorizedException(via the error handler of thesubscribe()call).This issue only affects subscriptions (when using
subscribe()). This is not an issue when doing mutations or queries via theclient.graphql()API, so the problem is only withsubscribe()combined with OIDC.This seems to be because Amplify JS uses the wrong token for authorization of the WebSocket connection when using
subscribe(). We have setup a customtokenProvideraccording to documentation. In this case Amplify always uses theaccessTokenfrom thetokenProvider(with auth modeoidc) when it should use theidToken(to satisfy the AppSync endpoint OIDC auth).Context
aws-amplify(currently version 6.17.0) for connecting to an existing AWS AppSync GraphQL API (in a React application).What we have tried
graphql()call options (asauthToken) preceding thesubscribe()call. This did not help.Authorizationheader via Amplify GraphQL custom headers configuration for overriding the auth token, but that also does not affect what is used as the auth token for.subscribe()with OIDC.return { accessToken: decodeJWT(idTokenString), idToken: decodeJWT(idTokenString), }tokenProvideris supposed to return also theaccessToken(though I'm not sure if the access token is ever actually needed in this context)Related Amplify source code references
I tried checking the logic in Amplify JS source code to verify if there is any functional way to override the auth token used in this case and from what I can see there is currently no other way to do this except using a custom
tokenProviderthat always returns theidTokeneven when the code asks for theaccessToken(not sure if this can cause issues in some case whereaccessTokenwould really be needed).I basically checked the code paths starting from the client's (
GraphQLAPIClass).graphql()call which starts hereGraphQLAPIClass.graphql()callssuper.graphql()(effectivelyInternalGraphQLAPI.graphql()).subscribe()fromAWSAppSyncRealTimeProvidersuper.subscribe()(effectivelyAWSWebSocketProvider.subscribe())authTokenSubprotocoltoheader-${encodedHeader}which contains the token. The header contents including the token is fetched usingawsRealTimeHeaderBasedAuth()method from another fileawsRealTimeHeaderBasedAuth()method which, in case of auth modeoidcwill callawsAuthTokenHeader()method in the same file which always sets the auth token like this:Authorization: session?.tokens?.accessToken?.toString(),. <- THIS IS THE PROBLEM It should use idToken instead. The session is provided byfetchAuthSession()from Auth singleton.fetchAuthSession()usesgetTokens()which in effectively gets the tokens directly from the custom token provider like this:this.authOptions?.tokenProvider?.getTokens(options)Then browsing the code more I found this part packages/datastore/src/sync/processors/subscription.ts#L284 which I think is the related section which is used when you use the DataStore API and as you can see here it does seem to use the correct token (idToken) so probably this issue does not affect usage of DataStore API. But I did not dig very deep in DataStore and I have not tried the DataStore API myself.
Related issues
amplify mockrelated issue that also relates to wrong token usageExpected behavior
When using
client.graphql(...).subscribe(...)with AppSync endpoint using OIDC authorization mode, the connection should be authenticated without errors when there is atokenProviderconfigured according to documentation example.Reproduction steps
@aws_oidcauthorization mode (using Entra ID as OIDC provider for example).aws-amplifyv6 (npm package)Code Snippet
Log output
Details
aws-exports.js
No response
Manual configuration
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