Closed pgbradbury closed 3 years ago
Hello @pgbradbury :wave: I think the issue here is that Auth.currentAuthenticatedUser
is throwing an error so it's blocking your page and authenticator from rendering because there may not be a user currently logged in at first. I'm not very familiar with TS so I removed all the type references but here's what I did to getInitialProps
to get it working. I basically just wrapped auth.currentAuthenticatedUser
in a try/catch. This should allow your page to render instead of throwing an error on the server before it gets the chance to render.
static getInitialProps = async ({ Component, ctx }) => {
const auth = withSSRContext(ctx).Auth;
try {
const user = await auth.currentAuthenticatedUser();
console.log({ user });
return {
pageProps: {
...(Component.getInitialProps
? await Component.getInitialProps({
...ctx,
store,
})
: {
user,
}),
},
};
} catch (error) {
console.error(error);
}
};
Now when I log in, I get this logged from the server
user: CognitoUser {
username: 'a042760e-9b68-425d-9ea1-fa2efa783acb',
pool: CognitoUserPool {
userPoolId: 'eu-west-1_xxxxxxx',
clientId: 'xxxxxxxxxxxxxxxxxxxxxx,
client: [Client],
advancedSecurityDataCollectionFlag: true,
storage: [UniversalStorage],
wrapRefreshSessionCallback: [Function (anonymous)]
},
Session: null,
client: Client {
endpoint: 'https://cognito-idp.eu-west-1.amazonaws.com/',
fetchOptions: {}
},
signInUserSession: CognitoUserSession {
idToken: [CognitoIdToken],
refreshToken: [CognitoRefreshToken],
accessToken: [CognitoAccessToken],
clockDrift: 0
},
authenticationFlowType: 'USER_SRP_AUTH',
storage: UniversalStorage {
cookies: [Cookies],
store: [Object: null prototype]
},
keyPrefix: 'CognitoIdentityServiceProvider.XXXXXXXXXXXXXX',
userDataKey: 'CognitoIdentityServiceProvider.XXXXXXXXXXXXXXXXXXX.userData',
attributes: {
sub: 'a042760e-9b68-425d-9ea1-fa2efa783acb',
email_verified: true,
phone_number_verified: false,
phone_number: '+15558085788',
email: 'chris.github@gmail.com'
},
preferredMFA: 'NOMFA'
}
},
My page props logged from client
{
pageProps: {
user: CognitoUser {
username: 'a042760e-9b68-425d-9ea1-fa2efa783acb',
pool: [CognitoUserPool],
Session: null,
client: [Client],
signInUserSession: [CognitoUserSession],
authenticationFlowType: 'USER_SRP_AUTH',
storage: [UniversalStorage],
keyPrefix: 'CognitoIdentityServiceProvider.XXXXXXXXXXXXX',
userDataKey: 'CognitoIdentityServiceProvider.XXXXXXXXXXXX.userData',
attributes: [Object],
preferredMFA: 'NOMFA'
}
}
}
Hi,
You seem to have removed the redux wrapper as well. I wrapped the try-catch as you did around my existing code, however I still get an error
I am logging the following
1. getProps created store with state undefined
The user is not authenticated
3. getProps after dispatches has store state undefined
4. WrappedApp created new store with withRedux(_app_Aura) { initialState: undefined, initialStateFromGSPorGSSR: undefined }
props: {
'data-element': 'Component',
'data-source-file': '_app.tsx',
dispatch: [Function: dispatch]
}
I need the initial state to have the user object defined.
Also, the current state of the user is a logged in authenticated user.
Ah okay, gotcha. So your app was already able to render the authenticator and you were able to log in through the form but you get this error after logging in? I've yet to use Redux with Next.js and Amplify so I'm not sure if this is an Amplify or Redux issue but I will try to add Redux to my reproduction.
Not exactly, I am building this in a docker container. So I have previously logged in and it's dropped a cookie. I have then rebuilt the container, so the user still persists in the cookie but is dropped from the server. I have tweaked a couple of things (it was throwing an error after this due to no state, I moved that) and it seems to now work with the try-catch solution. I will try some more scenarios, but I think this has fixed it.
Nice! Yeah, please let me know if you're able to persist user data to your Redux store. I'll still try to work Redux into my reproduction so I'm more familiar with the design pattern. I'm currently looking through this
https://github.com/vercel/next.js/tree/canary/examples/with-redux
I started there but ended up using a library to help with the redux-nextjs integration
https://github.com/kirill-konshin/next-redux-wrapper
https://github.com/vercel/next.js/tree/canary/examples/with-redux-wrapper
OK, so I have tested this a few different ways. It seems to boil down to this at the moment.
On the first instance of a login the try and catch works, but no user is set, so the app breaks or you catch no user and do something.
If I refresh the app works. I assume this is all tied to amplify SSR logic and figuring out an authenticated user.
A hack would be to force a refresh on catching no user, but that is really bad. I thought the whole point of withSSRContext was to make this work. This is what I have put in place and it works, but its not good code.
Any ideas?
P.S. I upgraded aws-amplify to 4.1.1, it made no difference.
I'm thinking since you're using redux and dispatching that action from getInitialProps, maybe you should add the Hub listener from Amplify to a useEffect so the client can dispatch that action that updates the redux store with the current user on a sign in event?
The action from getInitialProps sounds to me like it will only work if the user's already logged in which means credentials have been set to localStorage and/or the cookie exists. So that action would be updating the redux store when a user either reloads the page or comes back to the site at a later time, etc, but that sign in event needs to trigger its own dispatch to the store as well which can be done through the Hub.
Sorry for the late response, I have been trying a few things out.
The problem is that the server-side doesn't seem to be working at all. I can try with a logged-in user or log in from a clean state, either way, it doesn't pick up the user server-side. I am not sure why as I thought withSSRContext (an amplify component) was supposed to do that as a first-class integration into next.js
Hi @pgbradbury I can't confidently say that this is an issue with Amplify as I don't have any issues using withSSRContext
outside of a redux project. I've cloned the with-redux-wrapper
example from the links you shared before and have been messing around with it a bit and while I can get my server side code to log the authenticated user, I haven't figured out how to properly store that data in my redux store and/or get it from the store in my components. So, I'm still trying to figure out how to use Amplify and Next-Redux-Wrapper in tandem myself.
Hi @chrisbonifacio understood, but i'm not sure its redux related. It might be though, I am still working on it too and will post a solution if I find one.
With regards to the Hub
listener, this seems to be very similar to redux. The reason I don't think it will work is that it will listen for an auth
event. However a user may already be logged in to the application and simply returning a few hours later. No event happens, but the app needs to find the user from the cookie.
@chrisbonifacio this issue seems related, it was bot closed but people are still posting to it. #6555
@chrisbonifacio, OK I have stripped it all back and taken the SSR out. I made the auth call in an index.tsx instead. I am still getting the user not authenticated error, I am getting the following more detailed error from the catch block on the async call.
[ERROR] 51:08.525 AuthError -
Error: Amplify has not been configured correctly.
The configuration object is missing required auth properties.
Did you run `amplify push` after adding auth via `amplify add auth`?
See https://aws-amplify.github.io/docs/js/authentication#amplify-project-setup for more information
followed by my catch console log
***** ERROR *****: The user is not authenticated
Could you let me know where you are at with this, I have not changed the configuration from when it was working? This github ticket was generated as requested from a paid AWS support ticket and time really is of the essence now as it has been weeks.
Code block from index.tsx
const getUser = async () =>
await Auth.currentAuthenticatedUser()
.then((user: CognitoUserInterface) => {
console.log("***** USER2 *****:", user)
dispatch({ type: "SETUSER", payload: user })
})
.catch((error) => {
console.log("***** ERROR *****:", error)
})
getUser()
My package.json now looks like this
"dependencies": {
"@aws-amplify/auth": "3.4.28",
"@aws-amplify/core": "^3.8.20",
"@aws-amplify/interactions": "^4.0.4",
"@aws-amplify/storage": "^4.2.1",
"@aws-amplify/ui-components": "^1.3.2",
"@aws-amplify/ui-react": "^1.0.5",
"@fullstory/browser": "^1.4.9",
"@material-ui/core": "^4.11.0",
"@material-ui/lab": "^4.0.0-alpha.56",
"@material-ui/pickers": "^4.0.0-alpha.12",
"@reduxjs/toolkit": "^1.6.0",
"@sentry/fullstory": "^1.1.5",
"@sentry/nextjs": "^6.7.0",
"@types/material-ui": "^0.21.8",
"@types/node": "^14.14.33",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"axios": "^0.21.1",
"date-fns": "^2.21.3",
"i18next": "^19.8.9",
"material-table": "^1.69.1",
"md5": "^2.3.0",
"next": "^11.0.1",
"next-redux-cookie-wrapper": "^2.0.1",
"next-redux-wrapper": "^7.0.2",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-dropzone": "^11.3.1",
"react-feather": "^2.0.8",
"react-i18next": "^11.8.9",
"react-image": "^4.0.2",
"react-redux": "^7.2.4",
"react-vis": "^1.11.7",
"redux-devtools-extension": "^2.13.9",
"redux-saga": "^1.1.3",
"styled-components": "^5.3.0"
},
@pgbradbury Okay, well this helps to narrow down the root issue. With those package versions I think the issue might actually just be duplicate dependencies between the scoped amplify packages which is usually fixed by simply upgrading all packages. How are you importing and configuring Auth
?
I removed aws-amplify
and replaced my dependencies with yours. I am importing and configuring like so without issues so far.
import { Auth } from "@aws-amplify/auth";
import { config } from "../amplify.config";
Auth.configure({ ...config });
With your dependencies, running the command from this page
resulted in this output, meaning these two are duplicated
@aws-amplify/core
@aws-amplify/ui-components
OK, so how is that happening, on my machine I just ran the command and it only shows
@aws-amplify/core
But it is only defined once in the package.json, the ticket I indicated to you says it fixed the issue by installing a specific version of the auth lib. What versions should be pinned to make this work and why are there conflicting libraries creating breaking changes.
I have upgraded to the latest versions which has removed the duplicates and I am rebuilding to see if that makes a difference.
Depending on what versions of the scoped packages you install, they are dependent on different versions of other packages. The latest versions should all be compatible with each other though. This is usually only an issue when incompatible versions of the scoped packages are installed, or trying to use them along with aws-amplify
. The Amplify class is designed as a singleton. and should only have one instance, otherwise there will be configuration issues.
@pgbradbury just wanted to give you a heads up as I just realized it. You will have to use aws-amplify
if using Auth (or any other SSR-supported amplify library) in an SSR context because withSSRContext
seems to only be exported from there. None of the scoped packages export it because it's a utility to re-instantiate each package per request.
Hey, still working on this build, please don't tag auto close by a bot, I will respond once I have something, as I said its a paid support ticket.
Thanks for the heads up on the withSSRContext
not being in the scoped packages, that seems like an oversight and should be in /core
I have only changed to the scoped packages as that has been suggested by others with this same issue as a possible solution, I was looking for you guys to provide a solution, there are a LOT of posts, tickets, requests relating to similar issues, but none of them seem to have a resolution, a lot just get bot closed. There is clearly an issue here, either in the code or the documentation. I appreciate the help so far but I would expect this to be escalated and resolved by now. This isn't a library that is built by people on their own time as a service to the community, its built by a dedicated team at AWS for which I pay for professional support and that support asked me to log this ticket as an escalation as they could replicate but not resolve.
I have changed the code in so many different ways now it is becoming difficult to understand if the root error is due to a change or the same original issue. Redux is a diversion, it was failing before hitting any redux code. I have now changed the code to try and get something working as you have been unable to resolve the original issue.
@pgbradbury Apologies. My intention was not for this to be auto-closed, it would take a week of no response for the stale bot to do so. I labeled it as such more for the notification I get on my project board because I can tell just by looking at the board another comment was made on the issue since the bot removed the label. I prefer it over the email notification because I get so many from github already and I spend most of my time on here anyway.
I agree that the issue must've been occurring even before Redux if you were already getting that Auth has not been configured correctly
error. However, I set up my reproduction app with the same Auth configuration you have, basically just used the amplify.config
file you provided and everything seemed to work both client and server side.
Unless I could take a look at your project repository, it's not obvious to me where the configuration might need to be corrected. I've seen some have issues with the way Amplify.configure
or Auth.configure
is being called and the properties set. Maybe that could be part of the problem?
This is the way I did it in _app.js
import { Amplify, Hub, withSSRContext } from "aws-amplify";
import { config } from "../amplify.config";
Amplify.configure({ ...config, ssr: true });
Here's my reproduction app which has no Redux code, just the use of withSSRContext
to pass each page the currently authenticated user as a prop. You just need to add the amplify.config
file you shared before for it to work. I added two other pages that logs the user from props and shows whether it got the user from the client or the server.
So, the way I saw the problem was that if you're using the Amplify sign in component, you don't get redirected to the next view so you're still on the same page. No request to the server was made for the user that just logged in and we're not storing this data anywhere. What I did was use the Hub to listen for the "signIn" event and set the user locally first just so I can keep the amplify sign in component and still get my user while remaining on the same page. This only happens once. If using a custom sign in form, you could simply redirect to the next page and your server side code should get the user and pass it through pageProps. My getInitialProps
function is still getting the user on the server side any time the user navigates to another page after the sign in form has been used.
Let me know if this helps and I apologize again for making it seem like this would be auto-closed.
Thanks will try this out.
I am not clear where the misconfiguration was introduced, for example, did it happen when I moved to the scoped packages.
I did get the code working in the index.tsx, returning a user, so now I'll try and get it working in _app.tsx
This is the call that is now working in index.tsx
const getUser = async () =>
await Auth.currentAuthenticatedUser()
.then((user: CognitoUserInterface) => {
dispatch({ type: "SETUSER", payload: user })
})
.catch((error) => {
console.log("***** ERROR *****:", error)
})
getUser()
@pgbradbury The Auth configuration issue was likely due to the mismatching versions between the scoped packages. The SSR and user state management was the original and separate issue, I believe. If you're using the Amplify UI components to login, you either need to save the response/user data to state locally or even just reload the page/redirect when the signIn
event happens from the Hub listener. Although, it sounded like you didn't want to have to reload the page on signIn, which is why I opted to save the response to local state in my app.
So, it's really just that one time 'signIn' event that needs to be handled before withSSRContext
can get who the current authenticated user is from the server on subsequent page requests.
Let me know how it's going and/or if my app/explanation helps to make sense of the sign in flow.
@chrisbonifacio
So I have still been struggling with this massively, so I completely replaced my _app.tsx and index.tsx with yours, with only minor code mods. Which is still the same underlying error.
This is what I got
Server console:
{ error: 'The user is not authenticated' }
{ user: { fromClient: true } }
Client console:
{user: {…}}
user:
fromClient: true
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
Screenshot:
Sorry to hear that. Would you be willing to jump on a call and take a look at this together? I think it might be easier to explain that way than over gh issues.
That would be amazing. What is the best way to organise that? I am on London time, but I'll happily do a call my evening time if that helps, assuming you are on Pacific time.
I'm on the East Coast of the U.S., so EST. I think you might have to go through the internal ticket you opened and set up a meeting through there or we could probably set something up faster if you want to email me at christopher.bonifacio@gmail.com 😄
Hey @pgbradbury , just wanted to follow up and see how it's going after our meeting. Let me know if there's anything else I can do to help.
@pgbradbury Going to close this issue for now since we were able to address the original issue by handling the thrown error when attempting to get the current Authenticated User when not currently logged in with getInitialProps
, as well as making sure the user data is being properly accessed and then passed as a prop, after successful login, to each page component and dispatched to the redux store.
That being said, it was a pleasure to jump on a call with you and work through it together. Please feel free to open a new issue with the details of any new blocker so we can track the different issues we're tackling. Thank you!
This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.
Looking for a help forum? We recommend joining the Amplify Community Discord server *-help
channels or Discussions for those types of questions.
Before opening, please confirm:
JavaScript Framework
React, Next.js
Amplify APIs
Authentication
Amplify Categories
auth
Environment information
Describe the bug
AWS Support asked me to raise this here as they can't find a solution and will escalate to you.
I am building a Typescript/Next.js/React/Redux app and I am trying to pass the user object via the props from the _app.tsx page
I get a
The user is not authenticated
error when callingcurrentAuthenticatedUser()
. I am not using federated login.Expected behavior
The user object is returned with the logged in user.
Reproduction steps
It happens as soon as you navigate to the base URL of the app.
Code Snippet
Log output
aws-exports.js
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
Package.json is the following