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

Server-Side Rendering (SSR) with Next.js #5435

Closed
7 tasks done
ericclemmons opened this issue Apr 16, 2020 · 108 comments
Closed
7 tasks done

Server-Side Rendering (SSR) with Next.js #5435

ericclemmons opened this issue Apr 16, 2020 · 108 comments
Assignees
Labels
API Related to REST API issues Auth Related to Auth components/category DataStore Related to DataStore category SSR Issues related to Server Side Rendering

Comments

@ericclemmons
Copy link
Contributor

ericclemmons commented Apr 16, 2020

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, and DataStore categories in your Next.js app!

  1. First, reinstall Amplify using the @preview tag on NPM:

    yarn add aws-amplify@preview
    # or
    npm install aws-amplify@preview --save
  2. If you're only accessing public data (e.g. API.graphql(listBlogs)), you're done! 😌

  3. 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:

    diff --git a/my-next-app/pages/index.tsx b/my-next-app/pages/index.tsx
    index 22b0c471..d7ce454b 100644
    --- a/my-next-app/pages/index.tsx
    +++ b/my-next-app/pages/index.tsx
    @@ -1,6 +1,6 @@
     import { CognitoUser } from '@aws-amplify/auth'
     import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api-graphql'
    -import { Amplify, API, Auth } from 'aws-amplify'
    +import { Amplify, withSSRContext } from 'aws-amplify'
     import { Authenticator } from 'aws-amplify-react'
     import { GetServerSideProps } from 'next'
     import Head from 'next/head'
    @@ -13,8 +13,6 @@ import { CreateTodoMutationVariables } from '../src/API'
    
     Amplify.configure(awsconfig)
    
     // 👇 Your frontend code is now "SSR-aware", giving the backend access to the current session
    +const { Auth, API } = withSSRContext()
    +
     export default function Home(props) {
    @@ -392,8 +390,6 @@ export default function Home(props: Props) {
     }
    
     export const getServerSideProps: GetServerSideProps = async (context) => {
       // 👇 Auth & API are now scoped to the _current_ user's session
    +  const { Auth, API } = withSSRContext(context)
    +

    For more information on withSSRContext, see feat(SSR): withSSRContext #6146.

  4. Last step! Let us know your experience in the comments below 👇

Milestones

  • SSR withAuthenticator (fix(aws-amplify-react): BREAKING - Remove "import '@aws-amplify/ui/dist/style.css'" #5138)

    aws-amplify-react and @aws-amplify/ui-react should at least render withAuthenticator and <AmplifyAuthenticator /> on the server. This was previously blocked by an import "...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.

  • SSG with API

    Support API calls (e.g. GraphQLAPI.graphql(...)) in getStaticProps.

  • SSR with API

    Support API calls (e.g. GraphQLAPI.graphql(...)) in getServerSideProps.

  • Next.js /pages/api/* routes with API

    Similar to SSG & SSR, these routes are lambdas that need to support API.graphql calls.

  • SSR with Auth (feat(@aws-amplify/auth): UniversalStorage for SSR #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.

  • SSR with DataStore (feat(@aws-amplify/datastore): Support SSR #5450)

    DataStore needs special attention (due to WebSockets) to work on the server, and calls to DataStore.query have to wait for the sync process to complete (otherwise results are initially empty).

  • [ ] SSR with Analytics (Analytics autoTrack "event" fails with Gatsby (SSR) #6208)

  • withSSRContext (feat(SSR): withSSRContext #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

@ericclemmons ericclemmons self-assigned this Apr 16, 2020
@dabit3
Copy link
Contributor

dabit3 commented Apr 17, 2020

@ericclemmons you are the man.

@ericclemmons ericclemmons added this to the Support SSR (Auth + API) milestone Apr 17, 2020
@sovattha
Copy link

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.

@ericclemmons ericclemmons added SSR Issues related to Server Side Rendering API Related to REST API issues Auth Related to Auth components/category DataStore Related to DataStore category labels Apr 20, 2020
@dabit3
Copy link
Contributor

dabit3 commented Apr 21, 2020

@sovattha check back with us soon, this is already being developed and tested.

@ericclemmons
Copy link
Contributor Author

@sovattha Do any of issues in https://github.com/aws-amplify/amplify-js/milestone/26 match your experience? withAuthenticator works with the latest release:

  1. 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
  2. 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 🙏

@sovattha
Copy link

I'll keep an eye on your progress guys. The progress of the project is exciting. Thanks for noticing about the milestone.

@alexandrzavalii
Copy link

alexandrzavalii commented Apr 22, 2020

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?

@ericclemmons
Copy link
Contributor Author

@alexandrzavalii I saw the same thing here: #5293 (comment) (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!

@alexandrzavalii
Copy link

alexandrzavalii commented Apr 22, 2020

@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

@ericclemmons
Copy link
Contributor Author

@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 :)

@alexandrzavalii
Copy link

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 ericclemmons linked a pull request Apr 29, 2020 that will close this issue
7 tasks
@TimNZ
Copy link

TimNZ commented May 11, 2020

@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.

@ericclemmons
Copy link
Contributor Author

@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.

@TimNZ
Copy link

TimNZ commented May 11, 2020

@ericclemmons thanks for quick response.

The use case is the classic that every framework has a flow for:

  • user signs in -> verified on server -> server returns id/credentials/tokens
  • client 'caches' this - cookie, localStorage etc
  • clients call servers, identification data is included in calls to server

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[''],
and in Next.js getInitialProps for SSR

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?

@crash7
Copy link

crash7 commented May 11, 2020

@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.

@TimNZ
Copy link

TimNZ commented May 11, 2020

@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.

@TimNZ
Copy link

TimNZ commented May 12, 2020

@ericclemmons just a final follow on from my comment.

I agree with the frustration of the OP
#3495

I'm already considering moving away from Cognito after 3 days of pain.

@MontoyaAndres
Copy link

Thank you @ericclemmons and all the team behind this awesome work! 🦾

@omar-dulaimi
Copy link

Hello,
where can I find docs that explain SSR setup using Nextjs and Amplify?

@Danm72
Copy link

Danm72 commented Oct 17, 2020

@omar-dulaimi https://docs.amplify.aws/start/q/integration/next

@omar-dulaimi
Copy link

Thank you @Danm72

@flyandi
Copy link

flyandi commented Nov 7, 2020

This is awesome but the question is now, how do I use amplifyhosting to enable SSR? :-)

@ianmartorell
Copy link

This is awesome but the question is now, how do I use amplifyhosting to enable SSR? :-)

@flyandi That's tracked in this issue: aws-amplify/amplify-hosting#412

@ohlr
Copy link

ohlr commented Nov 9, 2020

One question regarding the blog

It suggests using api-keys to authorize api-access during the build of the app. If I do that, my builds stop working as soon the key expires (after 7 days).

Is there any solution to this? @ericclemmons

I tried IAM but without success.

@ericclemmons
Copy link
Contributor Author

@ohlr For real-world apps, I also opt for IAM instead. (Originally, the tutorial used IAM but it has a few more prompts that are easy to miss)

When you run amplify add api or amplify update api and select IAM, but be sure to set the Create/Read/Update/Delete permissions correctly. You'll want the IAM account to read-only, and leave Cognito User Pools to the write/delete operations.

@ohlr
Copy link

ohlr commented Nov 9, 2020

@ericclemmons Thank you for the hint with the permissions. I previously did not configure iam as provider.

      #schema.graphql
      { allow: groups, groups: ["Admin"] }
      { allow: public, operations: [read], provider: iam }

@ericclemmons
Copy link
Contributor Author

@ohlr Of course! I should've mentioned that or shared this link:

https://docs.amplify.aws/cli/graphql-transformer/auth#public-authorization

@hugomn
Copy link

hugomn commented Nov 10, 2020

Thanks for the great work adding SSR to amplify! I use apollo-client to handle our graphql queries and state, so I cannot use the SSR.API.graphql() call. I'd need to get the session jwt token on the server and create an apollo client with it. Is it currently possible? I tried something like below, without sucess.

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { Auth } = withSSRContext(context)
  const session = await Auth.currentSession()
  const token = session.idToken.jwtToken
  console.log('Apollo client token: ', token)
  return {}
}

Any ideas? Thanks!

@MontoyaAndres
Copy link

MontoyaAndres commented Nov 17, 2020

I'm trying to execute Auth.signOut but sometimes the user is logout and sometimes not. This is what I'm doing:

  const signOut = () => {
    Auth.signOut().then(() => {
      router.reload();
    });
  };

When the user executes the function signOut, the page is reloaded, then the /page/MySecretPage.tsx executes this method to validate if the user has access or not:

export async function getServerSideProps(context) {
  await withAuth(context);

  return {
    props: {},
  };
}

This is the function withAuth:

import { withSSRContext } from 'aws-amplify';

export const withAuth = async context => {
  try {
    const SSR = withSSRContext(context);
    const user = await SSR.Auth.currentAuthenticatedUser();

    return user;
  } catch (error) {
    context.res.writeHead(301, { Location: '/signin' });
    context.res.end();
  }
};

What can I do? :/

@dbhagen
Copy link

dbhagen commented Nov 18, 2020

Wow, I'm just hitting the same issue, @MontoyaAndres! Amazing timing. Out of curiosity, are you using SSO of any kind?

@MontoyaAndres
Copy link

@dbhagen I'm not the only one, that's great :), yes, I'm using sso. I tried to combine it with SSR but it didn't work, also sometimes the method on the function getServerSideProps does not work, and throw me to login...

@dbhagen
Copy link

dbhagen commented Nov 18, 2020

@MontoyaAndres so Eric asked me to fork Nader's repo for NextJS and recreate in that. I've done so here: https://github.com/dbhagen/next.js-authentication-aws

I'll admit, the example worked fine until AWS Auth/WebUI/SSO was added. If you want to be added to that repo to set up a specific page/use case, I'd be happy to add you.

@MontoyaAndres
Copy link

MontoyaAndres commented Nov 18, 2020

I've seen the code and it's implementing exactly the same I'm doing now. So, I think I'll wait until this problem with AWS Auth/WebUI/SSO is fixed :)

I don't know if is something about this issue vercel/next.js#16977

@ericclemmons
Copy link
Contributor Author

@MontoyaAndres I love the detail you’ve put into this

Can you copy/paste this info into a new issue so we can track and address it directly?

I’ve been able to validate SSR behavior with my samples locally, but there’s something amiss here...

@MontoyaAndres
Copy link

Hey @ericclemmons , yep, here it is #7223

@devcshort
Copy link

devcshort commented Apr 19, 2021

I'm currently using Amplify's SSR functionality in production for WA 211 and haven't run in to any issues yet. We love how quick and easy it is to set up.

I was slightly concerned that using withSSRContext and SSR.Auth.currentAuthenticatedUser() may not be secure. Is the data this retrieves fully from the server side and can't be changed on the client prior to the requests being sent? Is it secure to use the user sub directly from this method or any of the other methods as long as we're using withSSRContext?

To put it in to context, I'm not using Lambdas or any external server, I'd ideally like to use Next.js built in API functionality to handle reading and writing to my database.

@kylekirkby
Copy link

It's worth noting that if you are trying to get SSR working with anonymous Cognito IAM creds and you're using AppSync, make sure the authMode is set to AWS_IAM for anonymous credentials. This seems to be implicitly set when not using SSR so unsure what is funky logic is happening there...

    const resource = await SSR.API.graphql({
      query: resourcesBySlugCustom,
      authMode: authMethod,
      variables: {
        slug: resourceSlug,
      },
    });

@theelk801
Copy link

Hi, I have an issue that seems to be related to this and it's been really frustrating me. Is there any way I could get help on it?

@github-actions
Copy link

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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
API Related to REST API issues Auth Related to Auth components/category DataStore Related to DataStore category SSR Issues related to Server Side Rendering
Projects
None yet
Development

Successfully merging a pull request may close this issue.