Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hub.listen not firing when inside useEffect #13436

Open
3 tasks done
cekpowell opened this issue May 27, 2024 · 23 comments
Open
3 tasks done

Hub.listen not firing when inside useEffect #13436

cekpowell opened this issue May 27, 2024 · 23 comments
Labels
Auth Related to Auth components/category feature-request Request a new feature Hub Related to Hub category

Comments

@cekpowell
Copy link

cekpowell commented May 27, 2024

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

Other

Environment information

# Put output below this line

  System:
    OS: macOS 14.0
    CPU: (12) arm64 Apple M2 Pro
    Memory: 118.72 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.18.2 - ~/.nvm/versions/node/v18.18.2/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 9.8.1 - ~/.nvm/versions/node/v18.18.2/bin/npm
    Watchman: 2024.05.02.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 125.0.6422.78
    Safari: 17.0
  npmPackages:
    @ampproject/toolbox-optimizer:  undefined ()
    @aws-amplify/adapter-nextjs: ^1.2.1 => 1.2.1 
    @aws-amplify/adapter-nextjs/api:  undefined ()
    @aws-amplify/adapter-nextjs/data:  undefined ()
    @babel/core:  undefined ()
    @babel/runtime:  7.22.5 
    @edge-runtime/cookies:  4.1.1 
    @edge-runtime/ponyfill:  2.4.2 
    @edge-runtime/primitives:  4.1.0 
    @emotion/react: ^11.11.4 => 11.11.4 
    @emotion/styled: ^11.11.5 => 11.11.5 
    @hapi/accept:  undefined ()
    @hookform/resolvers: ^3.4.2 => 3.4.2 
    @hookform/resolvers/ajv:  1.0.0 
    @hookform/resolvers/arktype:  1.0.0 
    @hookform/resolvers/class-validator:  1.0.0 
    @hookform/resolvers/computed-types:  1.0.0 
    @hookform/resolvers/effect-ts:  1.0.0 
    @hookform/resolvers/io-ts:  1.0.0 
    @hookform/resolvers/joi:  1.0.0 
    @hookform/resolvers/nope:  1.0.0 
    @hookform/resolvers/superstruct:  1.0.0 
    @hookform/resolvers/typanion:  1.0.0 
    @hookform/resolvers/typebox:  1.0.0 
    @hookform/resolvers/valibot:  1.0.0 
    @hookform/resolvers/vest:  1.0.0 
    @hookform/resolvers/yup:  1.0.0 
    @hookform/resolvers/zod:  1.0.0 
    @mswjs/interceptors:  undefined ()
    @mui/material: ^5.15.17 => 5.15.17 
    @napi-rs/triples:  undefined ()
    @next/font:  undefined ()
    @opentelemetry/api:  undefined ()
    @svgr/webpack: ^8.1.0 => 8.1.0 
    @trivago/prettier-plugin-sort-imports: ^4.3.0 => 4.3.0 
    @types/eslint: 8.56.5 => 8.56.5 
    @types/node: ^20 => 20.12.12 
    @types/react: ^18 => 18.3.2 
    @types/react-dom: ^18 => 18.3.0 
    @types/voca: ^1.4.5 => 1.4.5 
    @typescript-eslint/eslint-plugin: 7.9.0 => 7.9.0 
    @typescript-eslint/parser: ^7.9.0 => 7.9.0 (7.2.0)
    @vercel/nft:  undefined ()
    @vercel/og:  0.6.2 
    acorn:  undefined ()
    amphtml-validator:  undefined ()
    anser:  undefined ()
    arg:  undefined ()
    assert:  undefined ()
    async-retry:  undefined ()
    async-sema:  undefined ()
    aws-amplify: ^6.3.4 => 6.3.4 
    aws-amplify/adapter-core:  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/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 ()
    babel-packages:  undefined ()
    browserify-zlib:  undefined ()
    browserslist:  undefined ()
    buffer:  undefined ()
    bytes:  undefined ()
    ci-info:  undefined ()
    cli-select:  undefined ()
    client-only:  0.0.1 
    commander:  undefined ()
    comment-json:  undefined ()
    compression:  undefined ()
    conf:  undefined ()
    constants-browserify:  undefined ()
    content-disposition:  undefined ()
    content-type:  undefined ()
    cookie:  undefined ()
    cross-spawn:  undefined ()
    crypto-browserify:  undefined ()
    cspell: ^8.8.1 => 8.8.1 
    css.escape:  undefined ()
    data-uri-to-buffer:  undefined ()
    debug:  undefined ()
    devalue:  undefined ()
    domain-browser:  undefined ()
    edge-runtime:  undefined ()
    eslint: ^8 => 8.57.0 
    eslint-config-next: 14.2.3 => 14.2.3 
    eslint-config-prettier: ^9.0.0 => 9.1.0 
    eslint-plugin-ft-flow: 2.0.3 => 2.0.3 
    eslint-plugin-jest: 28.5.0 => 28.5.0 
    eslint-plugin-jsx-expressions: 1.3.2 => 1.3.2 
    eslint-plugin-prettier: 5.1.3 => 5.1.3 
    eslint-plugin-react-hooks: 4.6.2 => 4.6.2 
    events:  undefined ()
    find-cache-dir:  undefined ()
    find-up:  undefined ()
    fresh:  undefined ()
    get-orientation:  undefined ()
    glob:  undefined ()
    gzip-size:  undefined ()
    http-proxy:  undefined ()
    http-proxy-agent:  undefined ()
    https-browserify:  undefined ()
    https-proxy-agent:  undefined ()
    husky: ^9.0.11 => 9.0.11 
    icss-utils:  undefined ()
    ignore-loader:  undefined ()
    image-size:  undefined ()
    is-animated:  undefined ()
    is-docker:  undefined ()
    is-wsl:  undefined ()
    jest-worker:  undefined ()
    json5:  undefined ()
    jsonwebtoken:  undefined ()
    jwt-decode: 3.1.2 => 3.1.2 
    lint-staged: ^15.2.2 => 15.2.2 
    loader-runner:  undefined ()
    loader-utils:  undefined ()
    lodash.curry:  undefined ()
    lottie-react: ^2.4.0 => 2.4.0 
    lru-cache:  undefined ()
    mini-css-extract-plugin:  undefined ()
    nanoid:  undefined ()
    native-url:  undefined ()
    neo-async:  undefined ()
    next: 14.2.3 => 14.2.3 
    next-intl: ^3.14.0 => 3.14.0 
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    picomatch:  undefined ()
    platform:  undefined ()
    postcss-flexbugs-fixes:  undefined ()
    postcss-modules-extract-imports:  undefined ()
    postcss-modules-local-by-default:  undefined ()
    postcss-modules-scope:  undefined ()
    postcss-modules-values:  undefined ()
    postcss-preset-env:  undefined ()
    postcss-safe-parser:  undefined ()
    postcss-scss:  undefined ()
    postcss-value-parser:  undefined ()
    prettier: ^3.2.5 => 3.2.5 
    process:  undefined ()
    punycode:  undefined ()
    querystring-es3:  undefined ()
    raw-body:  undefined ()
    react: ^18 => 18.3.1 
    react-builtin:  undefined ()
    react-device-detect: ^2.2.3 => 2.2.3 
    react-dom: ^18 => 18.3.1 
    react-dom-builtin:  undefined ()
    react-dom-experimental-builtin:  undefined ()
    react-experimental-builtin:  undefined ()
    react-hook-form: ^7.51.5 => 7.51.5 
    react-is:  18.2.0 
    react-markdown: ^9.0.1 => 9.0.1 
    react-qr-code: ^2.0.13 => 2.0.13 
    react-refresh:  0.12.0 
    react-server-dom-turbopack-builtin:  undefined ()
    react-server-dom-turbopack-experimental-builtin:  undefined ()
    react-server-dom-webpack-builtin:  undefined ()
    react-server-dom-webpack-experimental-builtin:  undefined ()
    regenerator-runtime:  0.13.4 
    sass-loader:  undefined ()
    scheduler-builtin:  undefined ()
    scheduler-experimental-builtin:  undefined ()
    schema-utils:  undefined ()
    semver:  undefined ()
    send:  undefined ()
    server-only:  0.0.1 
    setimmediate:  undefined ()
    sharp: 0.32.6 => 0.32.6 
    shell-quote:  undefined ()
    source-map:  undefined ()
    source-map08:  undefined ()
    stacktrace-parser:  undefined ()
    stream-browserify:  undefined ()
    stream-http:  undefined ()
    string-hash:  undefined ()
    string_decoder:  undefined ()
    strip-ansi:  undefined ()
    superstruct:  undefined ()
    svgo: ^3.3.2 => 3.3.2 
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    tty-browserify:  undefined ()
    typescript: ^5 => 5.4.5 
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    util:  undefined ()
    vm-browserify:  undefined ()
    voca: ^1.4.1 => 1.4.1 
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
    zod: ^3.23.8 => 3.23.8 ()
  npmGlobalPackages:
    @aws-amplify/cli: 12.11.1
    corepack: 0.19.0
    dotenv-cli: 7.4.2
    firebase-tools: 12.0.0
    get-graphql-schema: 2.1.2
    ios-deploy: 1.12.2
    jest: 29.7.0
    npm: 9.8.1
    serverless: 3.38.0
    vercel: 34.1.14

Describe the bug

TLDR

If i place a Hub.listen into a useEffect inside a component, the Hub never fires any events, and so I cannot handle the events and update my state accordingly. If i take the Hub out of the useEffect, it correctly fires, but I cannot update component state from outside of the useEffect as the component has not yet mounted.

Full Description & Context

I have a NextJS app that uses the Amplify SDK to handle Authentication. I am not using the Amplify CLI, but connecting to an existing AWS backend I have defined.

I have implemented sign in via social providers (Google, Apple and Facebook), and I am successfully re-directed to the provider, able to sign in, and be re-directed back to the app. Now, I would like to listen for the redirect auth event in my app so that I can update my system auth state (managed in a context) with the user's details, or handle errors if there are any, and then re-direct the user to the next screen.

As per the docs, I am trying to do this with a Hub.listen inside a useEffect in the page component. However, no event is ever fired when I am re-directed back to the app. If I take the Hub.listen out of a useEffect, or out of the component all together, then the event is fired, but I am no longer able to update my system context (as this must be done inside a component after mounting).

I am guessing the event is not firing inside useEffect because the app is unmounted when the redirect takes place, but then this raises the question of how I am meant to listen to re-direct auth events using the hub?

I have found this existing issue which has been closed, but recent comments suggest others are still facing this problem.

Expected behavior

The Hub listener should fire an event for signInWithRedirect.

Reproduction steps

  • Setup a NextJS app with amplify auth.
  • On your login screen, place a useEffect and a Hub listener which just logs the incoming payload, as done in the docs here. Also, place a Hub listener outside of the useEffect.
  • Call signInWithRedirect, and when redirected back to the app after sign in, the hub in the useEffect will not fire any events, but the one inside the useEffect will.

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

@cekpowell cekpowell added the pending-triage Issue is pending triage label May 27, 2024
@cekpowell cekpowell changed the title Hub.listen not firing signInWithRedirect events Hub.listen not firing when inside useEffect May 27, 2024
@amoffat
Copy link

amoffat commented May 28, 2024

I'm seeing a similar issue. In my case, I'm trying to listen for the tokenRefresh event, and I've set up my Hub listener inside a useEffect with an empty dependency list. Despite my tokens having a very short expiration (5 minutes), the tokenRefresh event never fires. I think we might have a similar underlying cause.

@cekpowell
Copy link
Author

@amoffat could be the case. I would imagine this issue is effecting all events rather than just the signInWithRedirect that we are wanting.

@israx
Copy link
Member

israx commented May 28, 2024

hello @cekpowell . Did you try placing Hub inside a useEffect in the layout file of your next.js App ?

@cekpowell
Copy link
Author

Hey @israx - does it specifically have to be the root layout for the app? Mine is a server component and I was under the impression making it a client component would be bad practice. Is this the setup you use?

Anyway, I tested it with a layout file for the signIn page and still no difference - the Hub is still not catching any events.

@israx
Copy link
Member

israx commented May 28, 2024

Hub was design to be used in SPA applications, meaning that Hub events will be dispatched in the same window object where it was initiated. So if Hub is initialized at the top of the element tree, then Amplify APIs called in any children components will be able to trigger the dispatch of events.

If you are calling an API on the server, and initializing Hub on the client, that would not work because there are 2 different instances of Hub.

@amoffat
Copy link

amoffat commented May 28, 2024

I'm using Hub client side in an SPA. Here's what I noticed: the auth event with the tokenRefreshed payload is only received by Hub when I call fetchAuthSession. Apparently this triggers Amplify to realize that the token is expired, which in turn fires the Hub event. But I was under the impression that the token refreshes would happen out-of-band, and that I could respond to those asynchronously through Hub, without triggering Amplify via fetchAuthSession

@cekpowell
Copy link
Author

Thanks @israx. I have tried placing the Hub in my AuthProvider which sits at the top of my app, and im my root layout.tsx file and im still finding that if the Hub is placed inside a useEffect, no events are being fired.

Should the Hub work in this case? I need to listen to auth events so I can update state, and I can only update state inside a useEffect.

@israx
Copy link
Member

israx commented May 28, 2024

@amoffat the library will check and emit the tokenRefresh event only when the access token is expired and the fetchAuthSession API is called. In order to support your use case the library needs to setup a listener and call fetchAuthSession when tokens are expired. Do you mind opening a feature request with us to keep track of this feature ?

@israx
Copy link
Member

israx commented May 28, 2024

@cekpowell could you share code snippets and show how you are setting up Hub and what APIs you are using to trigger the dispatch of events ?

@cekpowell
Copy link
Author

cekpowell commented May 28, 2024

Sure thing:

I have an AuthProvider component which provides auth state/update methods. it also has a Hub defined in a useEffect which should be able to listen to auth events. Here is a slimmed down version of it:

'use client'

import { decodeCognitoIdToken } from '@/utils/jwt'
import { AppSessionStorage, AppSessionStorageKeys } from '@/utils/sessionStorage'
import { Amplify } from 'aws-amplify'
import {
	signInWithRedirect as amplifySignInViaSocial,
	AuthError,
	fetchAuthSession,
	JWT,
} from 'aws-amplify/auth'
import {Hub} from 'aws-amplify/utils'
import { createContext, ReactNode, useCallback, useContext, useState } from 'react'

/**
 * * ------------------------------------------------------------------------
 * * MARK: Amplify Initialization
 * * ------------------------------------------------------------------------
 */

Amplify.configure(AMPLIFY_CONFIG, { ssr: true })

/**
 * * ------------------------------------------------------------------------
 * * MARK: Types
 * * ------------------------------------------------------------------------
 */

/** Supported providers for social sign-in in the app. */
export type AppSocialSignInProviders = 'Google' | 'Facebook' | 'Apple'

export interface SignInSocialParams {
	/** Social provider to sign the user into the app with. */
	provider: AppSocialSignInProviders
}

export enum SignInViaSocialErrors {
	/** Unknown error occured (i.e., error we do not account for). Can also occur if theres no internet connection. */
	unknown = 'unknown',
}

/**
 * * ------------------------------------------------------------------------
 * * MARK: Provider
 * * ------------------------------------------------------------------------
 */

export interface AppUserDetails {
	/** User's ID */
	userId: string
	/** User's email */
	email: string
	/** User's name */
	name: string
	/** URL to user's profile picture */
	picture?: string
	/** Access Token (JWT) */
	accessToken: JWT
	/** idToken (JWT) */
	idToken: JWT
}

export interface AuthContextType {
	/**
	 * Data
	 */

	/** Is there a user currently signed in? */
	isSignedIn: boolean
	/** `AppUserDetails` for currently signed in user. Undefined if no user is signed in. */
	userDetails?: AppUserDetails

	/**
	 * Operations
	 */

	/** Refreshes the user's auth state. */
	refreshAuthState: () => Promise<void>
	/** Sign the user in/up to the app via a social provider. */
	signInViaSocial: (params: SignInSocialParams) => Promise<void>
}

export const AuthContext = createContext<AuthContextType>({
	// data
	isSignedIn: false,
	userDetails: undefined,

	// operations
	refreshAuthState: async () => {},
	signInViaSocial: async () => {},
})

interface AuthProviderProps {
	/** Clear the cache of the `AuthProvider` on intialisation? */
	clearCache?: boolean
	/** Provider children */
	children?: ReactNode
}

/**
 * Provides and manages the authentication state of the app.
 */
export const AuthProvider = ({ children }: AuthProviderProps) => {
	// getting auth state via `useAuthProvider` hook.
	const auth = useAuthProvider()

	// provider
	return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>
}

/**
 * Hook that provides and manages the authentication state. We define this hook
 * to save defining all of the auth logic inside the `AuthProvider` component.
 */
const useAuthProvider = () => {
	/**
	 * Data
	 */

	// is the user signed in?
	const [isSignedIn, setIsSignedIn] = useState<boolean>(false)
	// details for currently signed in user
	const [userDetails, setUserDetails] = useState<AppUserDetails | undefined>(undefined)

	useEffect(() => {
		const unsubscribe = Hub.listen('auth', async ({payload}) => {
			console.log(payload)
		})

		return unsubscribe
	}, [])

	/**
	 * Operations
	 */

	/**
	 * Refresh Auth Context
	 */

	/**
	 * Refreshes the auth context based on the current auth session.
	 *
	 * Should be called whenever the users auth state changes and we need to update
	 * our context - e.g., sign in, sign out, etc.
	 */
	const refreshAuthState = useCallback(async () => {
		// fetching user tokens
		const authSession = await fetchAuthSession()
		const accessToken = authSession.tokens?.accessToken
		const idToken = authSession.tokens?.idToken
		const userAttributes = idToken && decodeCognitoIdToken(idToken.toString())

		// if user is signed in -> lets update state with user info
		if (accessToken && idToken && userAttributes) {
			setIsSignedIn(true)
			setUserDetails({
				userId: userAttributes.preferred_username,
				email: userAttributes.email,
				name: userAttributes.name,
				picture: userAttributes.picture,
				accessToken: accessToken,
				idToken: idToken,
			})
		}

		// if user is not signed in -> lets clear the state
		if (!accessToken || !idToken || !userAttributes) {
			setIsSignedIn(false)
			setUserDetails(undefined)
		}
	}, [])

	/**
	 * Sign Up
	 */

	/**
	 * Sign in via Social
	 */

	/**
	 * Sign the user in/up to the app via a social provider.
	 *
	 * **NOTE**: This method will re-direct the user to the social provider's login
	 * page, and they will then be send back to the app. You must handle this re-direct
	 * back to the app in order to complete the sign-in/sign-up.
	 */
	const signInViaSocial = async ({ provider }: SignInSocialParams) => {
		try {
			// setting the provider into storage (so we can access it after the re-direct)
			AppSessionStorage.setItem(AppSessionStorageKeys.ACTIVE_SOCIAL_PROVIDER, provider)

			// signing in via social
			await amplifySignInViaSocial({
				provider: provider,
				customState: 'my-custom-state',
			})
		} catch (e) {
			// cleaning up session storage
			AppSessionStorage.removeItem(AppSessionStorageKeys.ACTIVE_SOCIAL_PROVIDER)

			// throwing custom error
			throw new Error(SignInViaSocialErrors.unknown)
		}
	}

	/**
	 * Returning auth state.
	 */

	return {
		// data
		isSignedIn,
		userDetails,

		// operations
		refreshAuthState,
		signInViaSocial,
	}
}

/**
 * * ------------------------------------------------------------------------
 * * MARK: Hooks
 * * ------------------------------------------------------------------------
 */

/**
 * Returns the Auth context for the app.
 */
export const useAuth = () => {
	return useContext(AuthContext)
}

This AuthProvider is then wrapped around my RootLayout component like so:

const RootLayout = async ({
	children,
}: Readonly<{
	children: React.ReactNode
}>) => {
	// fetching app locale using next-intl
	const locale = await getLocale()

	return (
		<html lang={locale} suppressHydrationWarning>
			{/* remove default margin */}
			<body style={{ margin: 0 }}>
				<AuthProvider>{children}</AuthProvider>
			</body>
		</html>
	)
}

export default RootLayout

Finally, in my Sign In screen, I am calling the signInViaSocial method from the auth provider for one of Google, Facebook and Apple based on what button the user presses. This functionality works fine - I can log in, be redirected back to the app, its just that no event is fired from the Hub.

@amoffat
Copy link

amoffat commented May 28, 2024

@amoffat the library will check and emit the tokenRefresh event only when the access token is expired and the fetchAuthSession API is called. In order to support your use case the library needs to setup a listener and call fetchAuthSession when tokens are expired. Do you mind opening a feature request with us to keep track of this feature ?

Gotcha, so my Hub issue is unrelated to this issue then. I'll open the feature request.

@cwomack cwomack self-assigned this May 28, 2024
@cwomack cwomack added Auth Related to Auth components/category Hub Related to Hub category labels May 28, 2024
@cekpowell
Copy link
Author

@cwomack @israx - just a thought here. It seems the underlying issue is that the app is unmounted following signInWithRedirect, and thus the Hub is removed. Is it possible to perform the signInWithRedirect in a new tab to prevent this?

@israx
Copy link
Member

israx commented May 29, 2024

Yes you can signIn in a different tab as long as Hub.listen was called before signInWithRedirect API and the listener is still active. Probably you can stop the listener after a successful event and not on a component unmount

@cwomack cwomack added question General question and removed pending-triage Issue is pending triage labels Jun 4, 2024
@cwomack
Copy link
Member

cwomack commented Jun 4, 2024

@cekpowell, did the comments from @israx help unblock or clear the issue up here? Let us know if there is anything else that we can help with.

@cekpowell
Copy link
Author

Hi @cwomack - unfortunately not - this issue is still blocking me, and I have resulted to using a combination of a useEffect and local storage data.

Are you able to confirm if this is in-fact a bug with the SDK? It seems like a very crucial part of functionality that isn't working properly.

@r2data-systems
Copy link

r2data-systems commented Jul 26, 2024

Hi, I hope my comments are useful as this is new to me.
However, following this tutorial https://www.youtube.com/watch?v=wcSMnICY-_8 I encounter the same problem! Using the code from the github library specified in the video i.e. https://github.com/ErikCH/CodeFirstTypeSafety I can get it working, but effectively I'm regressing to the previous version specified in the video.
And believing its a version problem, here's the diff of my two package.json's

<   "name": "NOT WORKING,
---
>   "name": "WORKING",
12,15c12,15
<     "@aws-amplify/adapter-nextjs": "^1.2.7",
<     "@aws-amplify/ui-react": "^6.1.13",
<     "aws-amplify": "^6.4.0",
<     "next": "14.2.5",
---
>     "@aws-amplify/adapter-nextjs": "^1.0.8",
>     "@aws-amplify/ui-react": "^6.0.7",
>     "aws-amplify": "^6.4.3",
>     "next": "14.0.4",
21c21
<     "@aws-amplify/backend-cli": "^1.2.0",
---
>     "@aws-amplify/backend-cli": "^1.2.1",
25,26c25,27
<     "aws-cdk": "^2.149.0",
<     "aws-cdk-lib": "^2.149.0",
---
>     "autoprefixer": "^10.0.1",
>     "aws-cdk": "^2.150.0",
>     "aws-cdk-lib": "^2.150.0",
30c31
<     "eslint-config-next": "14.2.5",
---
>     "eslint-config-next": "14.0.4",
32c33
<     "tailwindcss": "^3.4.1",
---
>     "tailwindcss": "^3.3.0",
34c35
<     "typescript": "^5.5.3"
---
>     "typescript": "^5.5.4"

Hope this helps!
C.

P.S. This turned out to be a caching problem; clearing the "token" cache in the browser, resolved the problem and to be fair the video explains this ...

@apettiigrew
Copy link

apettiigrew commented Aug 2, 2024

The same issue is happening to me with the same setup. Has this been verified as a real bug, or are we doing something wrong with the setup? I was blocked for a few days with this and had to opt for manually handling it. It would be nice to have an official guide, though.

@johnf
Copy link
Contributor

johnf commented Sep 30, 2024

I've spent a couple of days chasing this down. I see a race condition where the oAuth flow is run before Hub.listen is called, so we miss the events that have been dispatched.

I feel this is worse in SSR apps like Next.js, and more prevalent in production than dev.

Typical example code

// ConfigureAmplify.tsx
import { useCognitoHub } from './auth';
export ConfigureAmplify () => {
  useCognitoHub();

  return null;
};

// layout.tsx
const RootLayout = async ({ children }: Readonly<{ children: React.ReactNode;}>) => (
    <html lang="en">
      <body>
        <ConfigureAmplifyClientSide />

        <Providers>{children}</Providers>
       </body>
    </html>
  );
};

// auth.ts
export const useCognitoHub = (setIsAuthLoading: (value: boolean) => void) => {
  useEffect(() => {
    const removeListener = Hub.listen('auth', async ({ payload }) => {
      const { event } = payload;

      switch (event) {
        // DO IMPORTANT TUFF
      }
    }, []);
    
    return removeListener;
  });
};

I'm working around this issue by delaying the oAuth processing till the listener is read.

First this patch

// NOTE: We need to delay oAuth on the web till we are ready
diff --git a/dist/esm/singleton/Amplify.d.ts b/dist/esm/singleton/Amplify.d.ts
index 24136eff86d96e951991844ec24537c342f13a4a..b96746d046f66b42b8fc77bdc20d71728908af5e 100644
--- a/dist/esm/singleton/Amplify.d.ts
+++ b/dist/esm/singleton/Amplify.d.ts
@@ -34,7 +34,7 @@ export declare class AmplifyClass {
     getConfig(): Readonly<ResourcesConfig>;
     /** @internal */
     [ADD_OAUTH_LISTENER](listener: (authConfig: AuthConfig['Cognito']) => void): void;
-    private notifyOAuthListener;
+    notifyOAuthListener(): void;
 }
 /**
  * The `Amplify` utility is used to configure the library.
diff --git a/dist/esm/singleton/Amplify.mjs b/dist/esm/singleton/Amplify.mjs
index 2a19a00a10bb68a0b3d4898b34b58c42f2910928..9db400e5e235c77bc27f0ce3f94b9b5ad93ccced 100644
--- a/dist/esm/singleton/Amplify.mjs
+++ b/dist/esm/singleton/Amplify.mjs
@@ -53,7 +53,7 @@ class AmplifyClass {
             event: 'configure',
             data: this.resourcesConfig,
         }, 'Configure', AMPLIFY_SYMBOL);
-        this.notifyOAuthListener();
+        // this.notifyOAuthListener();
     }
     /**
      * Provides access to the current back-end resource configuration for the Library.

Then I modify

// auth.ts
export const useCognitoHub = (setIsAuthLoading: (value: boolean) => void) => {
  useEffect(() => {
    const removeListener = Hub.listen('auth', async ({ payload }) => {
      const { event } = payload;

      switch (event) {
        // DO IMPORTANT TUFF
       }
    });

    // NOTE: We have a small hack to remove the listner race condition
    // https://github.com/aws-amplify/amplify-js/issues/13436
    Amplify.notifyOAuthListener();

    return removeListener;
    };
  },. []);
};

I think long term we need to be able to pass an option to Amplify.configure e.g. `Amplify.configure(outputs, { ssr: true, delayOAuth: true });

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Sep 30, 2024
@HuiSF
Copy link
Member

HuiSF commented Sep 30, 2024

Hi @johnf you are correct about the potential race condition, and thank you for providing this work around.

Since Hub.listen() is called within useEffect() would move Amplify.configure() call into the same useEffect() after the call of Hub.listen() resolve the race condition? Since the Amplify.configure() is execute on mount of the root layout, I believe the configuration can be applied through out the client life cycle.

In addition, I am interested in what's your opinion on the following:

Today Amplify JS fires an OAuth listener as early as possible to complete an OAuth flow. What if we change the approach, instead of a eager listener, we provide a callable API, say completeInflightOAuthFlow(), you can call it as needed, so you will have control on the timing.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Sep 30, 2024
@johnf
Copy link
Contributor

johnf commented Sep 30, 2024

@HuiSF I'll give moving Amplify.configure() a try, I think in theory it would work fine for my use case since I don't do anything with amplify before then.

Today Amplify JS fires an OAuth listener as early as possible to complete an OAuth flow. What if we change the approach, instead of a eager listener, we provide a callable API, say completeInflightOAuthFlow(), you can call it as needed, so you will have control on the timing.

I think this would work well. The current default behaviour is probably fine for most folks (e.g. there are no issues in react native due to the nature of the platform), but being able to disable it and use the callable API would be perfect.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Sep 30, 2024
@ashika112 ashika112 removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 8, 2024
@cwomack
Copy link
Member

cwomack commented Dec 10, 2024

@johnf and @cekpowell, wanted to follow up and see if this issue is still blocking you (or anyone following this). Let us know if there's still assistance needed here, thanks!

@cwomack cwomack added the pending-community-response Issue is pending a response from the author or community. label Dec 10, 2024
@johnf
Copy link
Contributor

johnf commented Dec 10, 2024

@cwomack I'm not blocked but mainly because I'm working around it with a patch

I'm patching the code so I can call notifyOAuthListner myself.

Tthe approach suggested by @HuiSF would be a good fix for this.

diff --git a/dist/esm/singleton/Amplify.d.ts b/dist/esm/singleton/Amplify.d.ts
index 24136eff86d96e951991844ec24537c342f13a4a..b96746d046f66b42b8fc77bdc20d71728908af5e 100644
--- a/dist/esm/singleton/Amplify.d.ts
+++ b/dist/esm/singleton/Amplify.d.ts
@@ -34,7 +34,7 @@ export declare class AmplifyClass {
     getConfig(): Readonly<ResourcesConfig>;
     /** @internal */
     [ADD_OAUTH_LISTENER](listener: (authConfig: AuthConfig['Cognito']) => void): void;
-    private notifyOAuthListener;
+    notifyOAuthListener(): void;
 }
 /**
  * The `Amplify` utility is used to configure the library.
diff --git a/dist/esm/singleton/Amplify.mjs b/dist/esm/singleton/Amplify.mjs
index acaadde4240ac63ec630795f726858b9caec11ac..f7f72f8c678a25f96b3d39add3302deed6b54fd9 100644
--- a/dist/esm/singleton/Amplify.mjs
+++ b/dist/esm/singleton/Amplify.mjs
@@ -53,7 +53,7 @@ class AmplifyClass {
             event: 'configure',
             data: this.resourcesConfig,
         }, 'Configure', AMPLIFY_SYMBOL);
-        this.notifyOAuthListener();
+        // this.notifyOAuthListener();
     }
     /**
      * Provides access to the current back-end resource configuration for the Library.

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Dec 10, 2024
@cwomack
Copy link
Member

cwomack commented Dec 11, 2024

@johnf, thanks for following up and great to hear the workaround that @HuiSF suggested will do for now.

We'll update this issue to be a feature request since the workaround is essentially a departure from how we recommend using Amplify.configure() as early as possible within an app's lifecycle. These use cases need to be considered within React's useEffect() hooks or we need to have updated documentation detailing these scenarios.

@cwomack cwomack added pending-community-response Issue is pending a response from the author or community. feature-request Request a new feature and removed pending-maintainer-response Issue is pending a response from the Amplify team. question General question labels Dec 11, 2024
@cwomack cwomack removed their assignment Dec 11, 2024
@cwomack cwomack removed the pending-community-response Issue is pending a response from the author or community. label Dec 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Auth Related to Auth components/category feature-request Request a new feature Hub Related to Hub category
Projects
None yet
Development

No branches or pull requests

9 participants