Closed ericclemmons closed 3 years ago
@ericclemmons you are the man.
It took me a day to realize that Amplify does not work well with the SSR of Next.js.
Going to the competition (Auth0) for the time being.
@sovattha check back with us soon, this is already being developed and tested.
@sovattha Do any of issues in https://github.com/aws-amplify/amplify-js/milestone/26 match your experience? withAuthenticator
works with the latest release:
Create or Update pages/_app.tsx
with Amplify.configure
:
import '@aws-amplify/ui/dist/style.css'
import Amplify from 'aws-amplify'
import { AppProps } from 'next/app'
import awsconfig from '../src/aws-exports'
Amplify.configure(awsconfig)
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp
Wrap pages/index.tsx
with withAuthenticator
:
export default withAuthenticator(Home)
If you have any issues, feel free to create a new issue so I can track it here ð
I'll keep an eye on your progress guys. The progress of the project is exciting. Thanks for noticing about the milestone.
Hi @ericclemmons , thanks for taking a look at the issue!
I still see the error in Next.js ReferenceError: window is not defined
inside
/@aws-amplify/ui-react/lib/components.js:8:76
I am using "aws-amplify": "^3.0.8", "@aws-amplify/ui-react": "^0.2.3"
Should I be using unstable
?
@alexandrzavalii I saw the same thing here: https://github.com/aws-amplify/amplify-js/issues/5293#issuecomment-610673005 (But the app still works)
If your Next.js is getting a different TypeScript/compilation/breaking error, open up a new issue and tag me: @ericclemmons. I'm happy to take a look!
@ericclemmons in Chrome console I got this error as well
Error: Amplify has not been configured correctly.
This error is typically caused by one of the following scenarios:
No idea if it is caused by SSR or sthg else.
But I do have the Authenticator rendered on the screen
@alexandrzavalii Definitely open up an issue with some more about your usage (including how/where you're doing Amplify.configure
). I have working examples that'll make their way into the docs once we thoroughly test it, but I'd like to unblock you if I can :)
Hey @ericclemmons, Looking forward for your example! It would be very helpful to have SSR example. Like having a navbar rendering "signin" or "profile" depending on user status.
@ericclemmons I don't want to crap on anyone (or pick on you specifically), but I've spent many hours looking for best practice for AWS Amplify/Cognito on how to do authentication in the browser and pass user identifying token to server i.e. the most standard of JWT type stuff for standard REST calls that don't presume API Gateway + IAM auth on calls. Also people have been asking for guidance/support for SSR for years.
Surely there is a single doc/video on this somewhere?
I've finally sorted out extracting JWT version of Access token to pass in Authorization header, for decoding by Lambda functions, but finding out how to do all this was more painful than any other framework I've used in years.
Since Amplify by default doesn't store any session data in cookies, SSR doesn't have anything to work with. Even finding out this was pain.
Every other framework has this most basic of scenarios working pretty much out the box and well documented.
withAuthenticator doesn't address any of this, I'm unclear as to why you reference it above.
Appreciate your thoughts on this. I have read a lot of comments around the place that people have given up on Amplify because it's so hard.
@TimNZ I hear you. I really do. When I joined Amplify in October, I was equally surprised that SSR wasn't addressed.
It sounds like you went through the same pain-points I did on Friday (lack of cookie support), but we're very close to having this resolved.
Regarding withAuthenticator
, there are varying degrees of SSR support: withAuthenticator
successfully renders out of the box (whereas it used to not!). Your case is more complex, since it relies on sharing session state between the client & the server so that the server can make privileged calls.
I'd love to know more about your use-cases so that they're not overlooked! Can you share more about this scenario (especially if you have sample code)?
how to do authentication in the browser and pass user identifying token to server i.e. the most standard of JWT type stuff for standard REST calls that don't presume API Gateway + IAM auth on calls.
@ericclemmons thanks for quick response.
The use case is the classic that every framework has a flow for:
For a lot of frameworks/stacks, that's a session cookie. That cookie might be httpOnly if it's returned by a server in a standard page flow structure i.e. not a SPA
I think the pattern I'm seeing with many people struggling is the Amplify documentation is shockingly silent on showing how this standard flow works using Amplify. Even 3rd party guides barely address it. Even 3rd party packages often are silent on it when they are providing a work-around for some aspect.
So many people are getting stuck on trying to get the system to work the way they are used to. Amplify is engineered to work with a complete end to end AWS stack.
In my case (which is typical), I have an API deployed on API Gateway/Lambda, deployed via Serverless.
I 'expected' to authenticate using Amplify - doing it manually e.g. Auth.signIn etc, and then I could get a identifying session/user token to pass to the server in API calls.
I had just presumed that the 'session token' would be in managed in a cookie so it was available in Lambda event.headers.
Having done traditional client/server dev, and SPA - Feathers JS, Sails etc, this is what everyone is expecting, and Amplify is making real hard to understand how to replicate the flow.
Ignoring XSS concerns, now I just might extract the ID or Access token as JWT and store it in a cookie on successful signIn.
I can now process that in my Lambda via event.headers.cookie['
If I wanted to apply best practice for XSS mitigation I'd implement redirect flow to endpoint that set httpOnly cookie.
^^ standard stuff
So the issue is poor documentation on how to do the above which most people are expecting kind of just works, like how they are used to with other stacks.
I think this is the root problem.
What do you think?
@TimNZ sorry to jump in the middle of your discussion with @ericclemmons, but I have more or less the same kind of scenarios and here are my two cents (based on my wall head banging):
a) you could create your own session and store it with an http only cookie (I have this workaround with a GraphQL server, it's not ideal but cognito handles all the auth and on successful auth, there is a login mutation that receives the jwt token, and the server verifies the token and updates de session)
b) since you are using api gateway, you could just add an authorizer and send the access token that cognito returns in the headers, validate it in the authorizer and if everything is OK, your function will be invoked. Again, not ideal because you need to add the authorizer.
Disclaimer: I don't use Amplify directly but I do use the aws cognito identify package that amplify uses under the hood, the main problem while doing SSRs is that we need to preload the cognito store with the data we read from the token (that gets passed in the headers) without putting a lot of if browser conditions.
@crash7
My comments are why are so many people struggling with scenarios they just expect to work, not that they can't be solved, once you understand things.
Now I got 'expert' through the pain, I know how to address it many ways.
And the Amplify documentation doesn't help because it just mostly presumes you'll use the full AWS end-to-end flow including IAM authorizer etc.
@ericclemmons just a final follow on from my comment.
I agree with the frustration of the OP https://github.com/aws-amplify/amplify-js/issues/3495
I'm already considering moving away from Cognito after 3 days of pain.
I've updated the PR description with more information on how SSR support is progressing. If you have any feedback related to SSR support & features, please share!
If you have other feedback, you can raise an issue, start a discussion, or join our Discord community:
Hey @ericclemmons how can we use the /pages/api/*
routes with API.graphql
ð² can we use this feature right now?
@MontoyaAndres Yes, so long as the data is public (e.g. not requiring auth credentials, which is in progress)! Here's a snippet from a working example of mine:
// pages/api/blogs.tsx
import API, { graphqlOperation, GraphQLResult } from '@aws-amplify/api-graphql';
import Amplify from 'aws-amplify';
import { ListBlogsQuery } from '../../src/API';
import { listBlogs } from '../../src/graphql/queries';
import awsConfig from '../../src/aws-exports';
Amplify.configure(awsConfig);
export default async function getBlogs(req, res) {
const { data } = (await API.graphql(
graphqlOperation(listBlogs)
)) as GraphQLResult<ListBlogsQuery>;
res.send(data.listBlogs.items);
}
@ericclemmons, really encouraging to see progress on SSR for Auth :smile: It's been overlooked for too long!
Any ETA on when we can get to use it?
@ericclemmons is somewhere available example repo where we can see what part of work is done and how to setup it all?
+1
@adammanderson @svrcekmichal Unfortunately, not public yet! I'm addressing security concerns and don't want to risk code getting adopted without those addressed ð
@ericclemmons is there any hack to go around this issue https://github.com/aws-amplify/amplify-js/issues/5101 while waiting for this milestone to complete. I want to use Datastore with Next js in a non SSR way for the time being. Then go SSR when it's fully supported.
Edit: I posted a hack that happens to work for me: https://github.com/aws-amplify/amplify-js/issues/5101#issuecomment-653448655 @ericclemmons
@ericclemmons ok, no pressure, thanks for all the time you put into this :)
Hey guys I am interested in using nextjs with amplify(auth and api). There was an update/release yesterday to amplify-js. How will I know that nextjs is supported in any release/update?? And how will I know which features(e.g. SSG, auth, api) of Nextjs are currently supported on any release/update??
@apoorvmote You can continue following & commenting on this issue for progress: we'll update this once it's on latest
.
ð Good news though! I just shipped a Next.js preview to aws-amplify@preview
!
I've updated the description at the top (https://github.com/aws-amplify/amplify-js/issues/5435) with steps to get started. Auth
, API
, and DataStore
are actively developed against, but I'm tracking other categories at the top as well.
Give it a go in your app & comment below on your experience ð
This seems promising, and if done AND documented well this could be a great alternative to Auth0. We've been using Cognito at work and while I like the concept the documentation at times has been terrible. Though I find that a lot across Amazon development resources, so I hope this won't meet the same blocking points of "this doesn't seem to be documented, and if it is it's not obvious".
I'm happy I stumbled on this before I started a new project going the other route. Progress seems to be going well maybe I'll bite the bullet and try out Amplify before going another route.
Also, are there currently any plans, or updates, on how things like AppSync will work with SSR? API is fine, but AppSync is what I had begun to look at if I went the all AWS route.
I am also building static blog with Nextjs. And I will be adding my own commenting system with dynamodb and graphql. Also will be building my own custom forum with SSR. Unfortunately because of some constraints I have to connect to appsync with rest api + lambda function. When will amplify start supporting faster and cheaper HTTP API?
@firrae Currently we're focusing on Amplify-specific categories (e.g. Auth
, API
, and DataStore
), which will be actively tested against Next.js.
I have tested Auth
with withSSRContext
and it works as advertised. But I am using amp
pages for loging in with amp-access.
I need to build 3 Rest API endpoints
I make post request on each endpoint to get login status, submit login info and logout the user. Its pretty self explanatory. But the Auth
Stores cognito user information in local storage on browser.
How do I check login status at rest API with lambda?
At endpoint /check-auth-status
I am able to parse cookie but how do I do cognito auth with cookie inside the scope of AWS amplify.
Until amplify people enable cognito for amp-access I am just going to use OAuth on express directly and skip cognito alltogether. Probably use API key to access graphql or just use lambda auth on graphql.
@apoorvmote Is this with Next.js? (https://nextjs.org/docs/api-reference/next/amp)
Thanks for the effort put into SSR. I wonder if this will work for the standalone version of amazon-cognito-identity-js
?
@apoorvmote Is this with Next.js? (https://nextjs.org/docs/api-reference/next/amp)
@ericclemmons Yes. So Next.js makes it easy to implement amp. So the original specification/documentation is available on https://amp.dev/ . I am looking to build basic text commenting system in amp. That requires amp-access component to authenticate users.
For authentication I need to store JWT into a cookie. Because amp doesn't support easy access to local storage. Because of recent developments on same-site cookie. The cookies will become more secure.
I don't know what is the priority for you guys to support amp. So I have started learning CDK, cloudformation. So I get to implement HTTP API with my custom amp authentication. I won't be using amplify for this project.
@apoorvmote If you're using @preview
(https://github.com/aws-amplify/amplify-js/issues/5435#issuecomment-655660103), /check-auth-status
should be able to return the current user via something like:
import { Amplify, withSSRContext } from 'aws-amplify'
import { NextApiRequest, NextApiResponse } from 'next'
import awsconfig from 'src/aws-exports'
Amplify.configure(awsconfig)
export default async (req: NextApiRequest, res: NextApiResponse) => {
const { Auth } = withSSRContext({ req })
try {
const user = await Auth.currentAuthenticatedUser()
return res.status(200).json({ message: "Authenticated!" })
} catch (error) {
console.error(error)
return res.status(500).json({ error })
}
}
/login
& /logout
dedicated routes aren't supported as part of this preview because authentication is delegated to Cognito on the client.
Can you open a new issue as a Feature Request with your circumstance? It looks like amp-access
(https://amp.dev/documentation/components/amp-access/?format=websites) would need explicit consideration as an authentication flow vs. SSR support alone via Next.js.
@ericclemmons
I tried something similar. Instead of building API I was testing getServerSideProps
on login page just to verify if I am logged in on client then am I logged in on server? Something like following.
export const getServerSideProps: GetServerSideProps = async(context) => {
const { Auth } = withSSRContext(context)
let user: any = null
console.log('get server')
try {
user = await Auth.currentAuthenticatedUser()
console.log('ssr user', user)
} catch (err) {
console.log('error', err)
}
return {
props: {
user: JSON.parse(JSON.stringify(user))
}
}
}
I was logged in on client but on server user was null. I was able to get user on the same page on client like follows.
useEffect(() => {
async function getAuth() {
try {
const user = await Auth.currentAuthenticatedUser()
console.log(user)
} catch (err) {
console.log('front end error', err)
}
}
getAuth()
}, [])
I am new to nextjs
. So I don't know if I have implemented any of this wrong. I refreshed page many times. But there is user on client and user is null at the server.
Can you open a new issue as a Feature Request with your circumstance? It looks like amp-access would need explicit consideration as an authentication flow vs. SSR support alone via Next.js.
Yes I will open new issue.
The underlying issue is we all want better SEO. So google built amp that has built in best page experience. But developers pushed against google for forcing us to use amp and not use our favorite stack. So google launched core web vitals. So now amp is no longer the only option for better SEO. We can use our favorite stack and as long as we conform to core web vitals and other best practices explained at https://web.dev/
And we love amplify
because it has best practices built in so we don't have think about it.
Now if you can deliver on that then I will give up this amp nonsense and use Nextjs directly with amplify. Until then I will build my own infrastructure with cdk and cloudformation and do server side rendering with nodejs and handlebarjs and use amp to build websites.
@apoorvmote It sounds like you're doing all the right things! One last check: does your client-side code also use withSSRContext
? Otherwise, the credentials aren't made available to the server:
import { Amplify, withSSRContext } from 'aws-amplify'
import awsconfig from 'src/aws-exports'
Amplify.configure(awsconfig)
const { Auth, API } = withSSRContext()
export default function Home() {
Otherwise, I'd need to check to see if Next.js' AMP behavior changes things somehow...
@ericclemmons Does it make any difference with
import { Amplify, withSSRContext } from 'aws-amplify'
import config from '../src/aws-exports'
Amplify.configure(config)
export default function Homepage({ user }: iProps) {
const { Auth } = withSSRContext()
vs
import { Amplify, withSSRContext } from 'aws-amplify'
import config from '../src/aws-exports'
Amplify.configure(config)
const { Auth } = withSSRContext()
export default function Homepage({ user }: iProps) {
Because I am using the first version.
@apoorvmote You only need one instance of Auth
on the client, so the first is preferred. The second version you're using would re-create Auth
(and all of Amplify) with each re-render of Homepage
.
Even though it's less optimal, you're doing the right thing: so long as the authentication flow completes successfully on the client, the server should then be able to access the current user.
@ericclemmons Thanks for clarification. Nextjs does show some promising features. But I am working on to somehow make amp-access work with whatever technology that works. Then transfer the knowlege to upgrade to Nextjs with amplify. And twist Amplify to fit my workflow.
@ericclemmons I'm using similat setup to what @apoorvmote is using here with no luck to get user on sever side.
What I noticed is that when I console.log Auth class in browser, there's multiple cognito cookies in UniversalStorage, but when console logged in getServerSideProps, UniversalStorage's store is almost empty, with all the cognito cookies missing. Do you maybe have some example working repo somewhere?
Posting here also my code, I've got auth route and 2 separate routes, one with SSR and one client side. On client side I've got "Hi, svrcekmichal@gmail.com" message, on server "no user":
// /pages/user-client.tsx
Amplify.configure(awsConfig)
const { Auth } = withSSRContext()
export default function AuthPage() {
const [loading, setLoading] = useState(true)
const [user, setUser] = useState(null)
useEffect(() => {
async function getAuth() {
try {
setUser(await Auth.currentAuthenticatedUser())
setLoading(false)
} catch (e) {
console.error(e)
setLoading(false)
}
}
getAuth()
}, [])
if (loading) {
return 'Loading...'
}
if(!user) {
return 'No user'
}
return (
<h1>Hi, {user.attributes.email}</h1>
)
}
and
// /pages/user-server.tsx
Amplify.configure(awsConfig)
export default function User({ user }) {
if(!user) {
return 'No user'
}
return (
<h1>Hi, ${user.attributes.email}</h1>
)
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const { Auth } = withSSRContext(context)
let user = null
try {
user = await Auth.currentAuthenticatedUser()
} catch (e) {
console.error(e)
}
return {
props: {
user: JSON.parse(JSON.stringify(user))
}
}
}
@ericclemmons I have bit of off topic suggestion. If I understand correctly you and others are working on Auth and API for NextJS with Amplify.
If you think about it then CDK and Amplify are very similar. If you and/or others could build CDK templates for using NextJS for Auth and API as you guys work on Amplify then it would be beneficial to lot of people(mainly me LOL). And you guys could upload them to aws-cdk-examples
I understood CDK lot faster by studying templates than reading documentation. Any partial template can save me a lot of time and headache.
Assuming I get amp-access working. I myself am planning to blog about bunch of CDK templates. Either way thanks for reading my suggestion.
Is it possible to add withSSRContext
to @aws-amplify/auth
as well, so that you don't have to require the whole aws-amplify
package in case you only need auth?
The aws-amplify
package is huge: 113 kB uncompressed, 29 kB compressed!
@asterikx I'll look into it. So we're comparing the same metrics, what steps are you taking to get those number? next build
?
@ericclemmons I got that number from the VSCode Import Cost extension. I'm not sure what the final impact on the bundle is.
My other regular dependencies have ~7-15 kB uncompressed, e.g. react
has 6.3 kB, emotion
(CSS-in-JS) has 15.4 kB. When you look at BundlePhobia, the numbers are even worse.
react@16.13.1
: 6.3 kB/2.6 kB (minified/gzipped)
emotion@10.0.27
: 15.4 kB/5.8 kB (minified/gzipped)
axios@0.19.2
: 13.1 kB/4.4 kB (minified/gzipped)
aws-amplify@3.0.18-preview.46
: 852 kB/207 kB (minified/gzipped)
aws-amplify@3.0.23
: 931 kB/213 kB (minified/gzipped)
@asterikx I would take those numbers with a grain of salt: the build output is what matters most and is where tree-shaking is visible.
Amplify v3 was built specifically with this in mind: https://twitter.com/ericclemmons/status/1240784212241846275
(Still, I am monitoring the impact of client-only aws-amplify
vs. SSR aws-amplify
with this PR)
Hey @ericclemmons, thanks for the work you're putting into getting SSR working! If I understood correctly, all the improvements that are being made to make Amplify work with Next.js will still require the final app to be hosted using Vercel, since Amplify Console doesn't support SSR. Is that right? Or how would this integrate with Amplify Console? I'm currently developing a React SPA that doesn't use Next.js and looking for the recommended way to add SSR for SEO purposes.
If I understood correctly, all the improvements that are being made to make Amplify work with Next.js will still require the final app to be hosted using Vercel, since Amplify Console doesn't support SSR. Is that right?
Vercel is one way to host your app. You can leverage the Serverless framework or just host it wherever you want to by following their docs
@ianmartorell you can upvote this issue for amplify console support ;) https://github.com/aws-amplify/amplify-console/issues/412
Vercel is one way to host your app. You can leverage the Serverless framework or just host it wherever you want to by following their docs
@m5r Thanks for the Serverless blog post link! Although I'd really like to host my app with the Amplify Console since I'm already using Amplify for everything else. And going for a node.js server just because I need SSR when I've already gone the full serverless architecture route doesn't feel great :)
@ianmartorell you can upvote this issue for amplify console support ;) aws-amplify/amplify-console#412
@ChristopheBougere Done!
Given Next.jsâ popularity, customer impact, and being a hybrid framework supporting both SSR & SSG, solving for Next.js first unblocks other frameworks (e.g. Gatsby, Nuxt).
There is additional work to be done for CLI & Console support (particularly around deployments), but this Epic focuses on JS support.
This feature is live! See the Getting Started tutorial and SSR docs for the latest!
Public Preview
We'd love your help testing out Amplify's
API
,Auth
, andDataStore
categories in your Next.js app!First, reinstall Amplify using the
@preview
tag on NPM:If you're only accessing public data (e.g.
API.graphql(listBlogs)
), you're done! ðFor secure access the current user on the server, you'll need to scope Amplify to the current request (and only that request) using our new
withSSRContext
helper:For more information on
withSSRContext
, see https://github.com/aws-amplify/amplify-js/pull/6146.Last step! Let us know your experience in the comments below ð
Milestones
[x] SSR
withAuthenticator
(#5138)aws-amplify-react
and@aws-amplify/ui-react
should at least renderwithAuthenticator
and<AmplifyAuthenticator />
on the server. This was previously blocked by animport "...css"
, but with Amplify v3 this was moved to customer apps to import.This doesn't mean session state yet, but unblocking the SSR process.
[x] SSG with
API
Support API calls (e.g.
GraphQLAPI.graphql(...)
) ingetStaticProps
.[x] SSR with
API
Support API calls (e.g.
GraphQLAPI.graphql(...)
) ingetServerSideProps
.[x] Next.js
/pages/api/*
routes with APISimilar to SSG & SSR, these routes are lambdas that need to support
API.graphql
calls.[x] SSR with
Auth
(#5710)Support shared client & server state so that authentication flows on the client share credentials with the server (via a subset of cookies). Calls to
Auth.currentAuthenticatedUser()
should succeed on the server the same way they do on the client.[x] SSR with
DataStore
(#5450)DataStore
needs special attention (due to WebSockets) to work on the server, and calls toDataStore.query
have to wait for the sync process to complete (otherwise results are initially empty).[ ] SSR with Analytics (https://github.com/aws-amplify/amplify-js/issues/6208)[x]
withSSRContext
(https://github.com/aws-amplify/amplify-js/pull/6146)Related milestone: https://github.com/aws-amplify/amplify-js/milestone/26 Related issues: #5101, #4178, #3741, #1613, #3278, #5121, #5097, #4972, #2230, #4990, #4851, #5293, #5435, #5322, #3854, #3053, #3348, #5138, #4311, #4305, #4207, #3037, #992