Closed ericclemmons closed 3 years ago
Tried the @preview
versions of @aws-amplify/auth
and @aws-amplify/core
out with Next.js and I get:
error - ./node_modules/@aws-sdk/client-cognito-identity/dist/es/runtimeConfig.browser.js
Should not import the named export 'name' (imported as 'name') from default-exporting module (only default export is available soon)
with both packages. When I got into the node_modules
and into runtimeConfig.browser.js
and instead of importing the name
and version
from package.json
I hardcode them, the code works. @ericclemmons you might need to change the way you bundle these packages ð€ .
@janhesters Can you share more of your code & your Next.js version? The demo app I'm running integration tests against uses aws-amplify@preview
as a dependency to access the withSSRContext
helper and haven't experienced that.
@ericclemmons will this work if someone creates a completely static app with next export
?
@ericclemmons will this work if someone creates a completely static app with
next export
?
If you create a completely static app you don't need server side rendering anymore.
@jfryerocv Yes, @ianmartorell is correct. The majority of Amplify worked with Next.js' SSG since v3 was released earlier this year.
There is some work here to support DataStore within that environment, but otherwise the withSSRContext
isn't needed at all for SSG.
@ericclemmons I tried both, running @preview
and the stable one and both display that behaviour. Not using withSSRContext
because we use sagas. Instead we use Auth
but all the code is only called on the client-side.
I have an issue here, which is for another problem, but has details about my Next.js setup.
@janhesters I'll check out https://github.com/aws-amplify/amplify-js/issues/6555, since that looks like the crux of the problem (and can be tested in CRA to reduce this being SSR-specific).
@ericclemmons I think #6555 might have a different reason though. I'll try and create a minimal setup for https://github.com/aws-amplify/amplify-js/issues/5435#issuecomment-672741212 soon ð
It would be great to know if you have plans to support Next.js incremental static site generation and when?
@dberhane This PR supports both SSG & SSR! If you're using the Amplify API
category for your data, I believe aws-amplify@master
will already work with SSG. For a static site (SSG), Auth
will happen on the client & should work as usual.
is there a specific deadline for this PR? i am planing to use amplify with Next JS for my upcoming project and would like to know if this would be ready within 1 or 2 months? if so this would help me a lot as i can start implementing my business logic and UI, and add amplify once this PR is merged. Really appreciate the work you guys are doing here. Thanks.
I am having difficult time serving api at path mydomain.com/api/*
because I am serving static site at path mydomain.com/
.
Let me prefix by saying I am using CDK and cloudfront/s3 for static site. And I am not able to forward request from cloudfront to api gateway. Cloudfront catches api path requests and returns 404 not found. I have made issue with CDK and they are working on it.
But tomorrow if I give up CDK/Cloudformation and go back to amplify with Nexjs. Is it possible to create custom domain api like mydomain.com/api/*
and have static site on mydomain.com/
without any conflicts and on HTTPS connection?
I just wanted to say thanks for the hard work being done here. Just got Next working with Amplify Auth!
@ericclemmons Hello , is there any plan to do something similar for nuxtjs ?
First of all, thanks a lot @ericclemmons for all the work being done here. I have been able to get the authentication working on a page using SSR and the documentation you provided here. I have a question though, I was planning to use React Context to store if the user has been authenticated or not. This would allow me to conditionally choose layout and also make other logic for pages based on the authentication status of the user.
However, when I am trying to use the following code in the getInitialProps
in my _app.tsx
:
const { Auth } = withSSRContext(context);
let isUserAuthenticated = false;
try {
const user = await Auth.currentAuthenticatedUser();
if (user) {
isUserAuthenticated = true;
}
} catch (e) {
console.error(e);
}
The result always bring a non authenticated user. However, if I use the getInitialProps
of the actual page, this will result in isUserAuthenticated
being true. Do you know why this could happen?
Thanks.
I'm using this preview and got Authentication up and running.
However I cannot seem to get API working trough the withSSRContext
.
When I do the following:
const { Auth, API } = withSSRContext();
API is null, even though the API works fine using import { API } from "aws-amplify";
.
I'm using "aws-amplify": "^3.0.18-preview.46",
Is there something I can do to debug this?
Another question: when will the preview get a version update? The current version is already at 24 :)
Looking forward to get this fully up and running!
@loukmane-issa Can you help me understand exactly what's happening here?
The result always bring a non authenticated user. However, if I use the getInitialProps of the actual page, this will result in isUserAuthenticated being true. Do you know why this could happen?
It sounds like, on the server, the user isn't authenticated. But running that logic on the client works (as expected)? I did find a bug recently with this, but haven't updated @preview
. Let me know if if my understanding is correct!
@Nickman87 Can you share a snippet of your code? I was aware of this potential problem, but haven't been able to replicate. Here's my API
usage:
// pages/api/create-todo.tsx
import { Amplify, withSSRContext } from 'aws-amplify'
import { GraphQLResult } from '@aws-amplify/api-graphql'
import { NextApiRequest, NextApiResponse } from 'next'
import { CreateTodoMutationVariables, CreateTodoMutation } from '../../src/API'
import awsconfig from '../../src/aws-exports'
import { createTodo } from '../../src/graphql/mutations'
Amplify.configure(awsconfig)
export default async (req: NextApiRequest, res: NextApiResponse) => {
const { API } = withSSRContext({ req })
try {
const query = createTodo
const variables: CreateTodoMutationVariables = {
input: {
title: `New Todo ${new Date().toLocaleString()}`,
},
}
const { data } = (await API.graphql({
query,
variables,
})) as GraphQLResult<CreateTodoMutation>
return res.status(200).json({ data })
} catch (error) {
console.error(error)
return res.status(500).json({ error })
}
}
@Nickman87 If you're open to discussing this live, you can DM on me Twitter (@ericclemmons) or find me in our Discord: https://discord.gg/Px5YVfQ
@ericclemmons is a good practice to call the method Amplify.configure
just once in the page _app.js
or is necessary to call it on each page?
@ericclemmons about:
It sounds like, on the server, the user isn't authenticated. But running that logic on the client works (as expected)? I did find a bug recently with this, but haven't updated @preview. Let me know if if my understanding is correct!
I don't believe this is what's happening. I am not a Next.js expert, but from what I understand, The getInitialProps
will either run on the server or on the client depending if it's the first load. In both cases, the authentication is working properly if I place the authentication code within the get initial props of the page itself (e.g. pages/dashboard.tsx). However, if I use the getInitialProps
of _app.ts, I get no user from the currentAuthenticatedUser()
method. The main reason for doing it in the _app.ts is to have the user and the authentication status available within a Context and easily access this throughout my application.
As I just started development, I have refactored my app in order to not use authentication status within a context and have the check in each page getInitialProps
but I would like to go back using Context if it is possible.
I'm getting no current user when using the preview
Thanks so much @ericclemmons it works well in Next.js. I'm not completely done with the refactor, but if anyone needs a Next.js example, here is mine:
@preview
yarn add aws-amplify@preview
# Optional
yarn add hoist-non-react-statics
_app.js
// libs/withAmplify.js
import * as React from "react";
import { Amplify, withSSRContext } from "aws-amplify";
import hoistNonReactStatics from "hoist-non-react-statics";
const AmplifyContext = React.createContext();
export const useAmplify = () => React.useContext(AmplifyContext);
export const withAmplify = (ComposedComponent) => {
const WithAmplify = (props) => {
const { amplifyConfig, ...rest } = props;
const { Auth } = withSSRContext();
React.useMemo(() => {
Amplify.configure(amplifyConfig);
Auth.configure(amplifyConfig);
}, []);
return (
<AmplifyContext.Provider value={{ amplifyConfig }}>
<ComposedComponent {...rest} />
</AmplifyContext.Provider>
);
};
WithAmplify.getInitialProps = async (appContext) => {
const { Auth } = withSSRContext(appContext.ctx);
const amplifyConfig = {
Auth: {
region: process.env.AWS_AMPLIFY_REGION,
userPoolId: process.env.AWS_AMPLIFY_USER_POOL_ID,
userPoolWebClientId: process.env.AWS_AMPLIFY_USER_POOL_WEB_CLIENT_ID,
mandatorySignIn: true,
authenticationFlowType: process.env.AWS_AMPLIFY_AUTHENTICATION_FLOW_TYPE,
oauth: {
domain: process.env.AWS_AMPLIFY_OAUTH_DOMAIN,
scope: process.env.AWS_AMPLIFY_OAUTH_SCOPE,
redirectSignIn: process.env.AWS_AMPLIFY_OAUTH_REDIRECT_SIGN_IN,
redirectSignOut: process.env.AWS_AMPLIFY_OAUTH_REDIRECT_SIGN_OUT,
responseType: process.env.AWS_AMPLIFY_OAUTH_RESPONSE_TYPE,
},
},
};
if (typeof window === 'undefined') {
Amplify.configure(amplifyConfig);
Auth.configure(amplifyConfig);
}
return {
amplifyConfig,
};
};
return hoistNonReactStatics(WithAmplify, ComposedComponent);
};
_app.js
Wrap your App with withAmplify
HOC previously created.
// pages/_app.js
import * as React from "react";
import { withAmplify } from "../libs/withAmplify";
const MyApp = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};
export default withAmplify(MyApp);
// pages/login.js
import * as React from "react";
import { withSSRContext } from "aws-amplify";
import Router from "next/router";
const Login = (props) => {
const [state, _setState] = React.useState({
username: "",
password: "",
});
const setState = (newState) => _setState({ ...state, ...newState });
// ð Your frontend code is now "SSR-aware", giving the backend access to the current session
const { Auth } = withSSRContext();
React.useEffect(() => {
(async () => {
let user, currentSession;
try {
user = await Auth.currentAuthenticatedUser();
} catch (e) {
console.error("currentAuthenticatedUser error:", e);
}
try {
currentSession = await Auth.currentSession();
} catch (e) {
console.error("currentSession error:", e);
}
// if (currentSession) Router.push("/");
console.log("login.user --", user);
console.log("login.currentSession --", currentSession);
})();
}, []);
const handleChange = (e) => {
setState({ [e.target.id]: e.target.value });
};
const federatedSignIn = () => {
Auth.federatedSignIn();
};
const signIn = async (e) => {
e.preventDefault();
try {
await Auth.signIn(state.username, state.password);
Router.push("/");
} catch (err) {
if (err.code === "InvalidParameterException" || err.code === "NotAuthorizedException") alert("Incorrect username or password.");
else alert(err.message);
}
};
return (
<>
<h1>Login page</h1>
<form onSubmit={signIn}>
<input id="username" type="text" value={state.username} onChange={handleChange} />
<input id="password" type="password" value={state.password} onChange={handleChange} />
<button type="submit" onClick={signIn}>
Sign in
</button>
</form>
<br />
<button onClick={federatedSignIn}>Hosted UI</button>
</>
);
};
export default Login;
While digging deeper in my implementation, I was hoping to override the path of the cookies options via cookieStorage
, however, this has no effect on the CognitoIdentityServiceProvider
cookies. With the above example, the cookies set on signIn()
have their path automatically set to /login
. As a consequence, other pages can't have the logged in user on SSR.
Adding path: "/"
here https://github.com/aws-amplify/amplify-js/commit/a579c9b66eec99e771da92e35ed179bb4f2f984d#diff-ac47a9e216a032c74ba8bf10c36119b3R106 seems to solve this.
Is there any reason not to enable the path
of the cookies to be overriden?
Thanks in advance for your insight
@astenmies Good catch! I've locally updated UniversalStorage
to add path: "/"
.
There's not a compelling reason yet to allow that customization, as I'd like to get more real-world usage examples to figure out what signature UniversalStorage
should have. (The fact that it uses cookies behind the scenes is an implementation detail with it's own flaws)
@dberhane Are you referring to the revalidate
property in https://github.com/vercel/next.js/discussions/11552?
This effort is ensuring Amplify works within Next.js v9.3's SSG/SSR methods, so if you're deploying to Vercel (which takes advantage of revalidate
), this should work as expected ð
@MontoyaAndres I'd recommend putting Amplify.configure
in _App.js
. For /api
routes, I don't think that's used, so you'll need to duplicate Amplify.configure
in those files. (Or put it in a utility folder and import from there)
Thank you @ericclemmons !
@ericclemmons do you have any sample project you could share that shows everything working together?
I'm having a good bit of trouble and trying to piece together the configuration is a little tricky!
Really appreciate your work here!
Hi guys, I've made a short video showcasing how to use Auth
within getServerSideProps
https://www.youtube.com/watch?v=Fc5kgSq1lpw
Also here is the repo of a sample project I've made - authenticating and fetching data within getServerSideProps
.
https://github.com/WojciechMatuszewski/next-appsync-cdk
Everything is done using withSSRContext
. Thank you @ericclemmons for your awesome work!
@WojciechMatuszewski Hey! Code looks great. If you use user pool auth does it still work for you? Or what auth are you using for your schema/models?
For example: amplify update api
update auth user pool
Does it still work? I'm still getting 'no current user'
@Danm72 I'm using user pools since I'm using Cognito (see config.json
within frontend
directory). I cannot speak for amplify-cli as I do not use that tool.
@WojciechMatuszewski nice video, thanks for sharing!!
Edit: Just watched the whole video, amazing explanation! We will definitely be sharing this once the release is officially announced if that is ok with you.
First of all, great job for the feature!!
However, if you only use it for Auth, it looks like it adds a lot to the bundle size.
As withSSRContext
is imported from aws-amplify
, maybe it's because of a tree shaking problem?
@WojciechMatuszewski nice video, thanks for sharing!!
Edit: Just watched the whole video, amazing explanation! We will definitely be sharing this once the release is officially announced if that is ok with you.
Sure, no problem. Thanks!
Thanks @ericclemmons,
Just noticed, API.post works fine from client but server side API.post its throwing error with 'The security token included in the request is invalid.' using Auth,API from withssrcontext. Rest API is iam authenticated. But if i change the service cognito authorizer it does work. Any example with withssrcontext server API.post works with iam authenticated rest api.
The details in the above PR helped me a lot to get things running
ð Hi everyone!
Today, I'm proud to say that we've officially released SSR support for Amplify JS!
Twitter announcements:
Launch post:
Getting Started:
Documentation:
There have been a few changes since aws-amplify@preview
was published (see the resources above for more):
Configure your application with Amplify.configure({ ...awsExports, ssr: true })
so that it's "SSR-aware".
Client-side code should stay the same as before:
import { Amplify, API } from "aws-amplify"`;
...
const { data } = await API.graphql({ query, variables })
Using withSSRContext
on the client will hurt client-side bundle sizes!
Server-side code should use the new withSSRContext
utility:
import { Amplify, withSSRContext } from "aws-amplify";
...
export async function getServerSideProps({ req }) {
const SSR = withSSRContext({ req });
const { data } = await SSR.API.graphql({ query, variables });
...
withSSRContext
creates a new copy of Amplify
that's scoped to a single req
uest (including credentials!).
Check out the Getting Started tutorial for a working example of this usage!
If you're site is fully static (no getServerSideProps
), you can continue using Amplify Console!
However, if you're using getServerSideProps
or fallback: true
, there are other options:
- Until [Amplify Console supports SSR](https://github.com/aws-amplify/amplify-console/issues/412) (be sure to upvote & comment!), we've used & tested [deploying with serverless framework](https://docs.amplify.aws/start/getting-started/hosting/q/integration/next).
- Of course, several customers are [deploying with Vercel](https://nextjs.org/docs/deployment) and that should continue working, too.
With today's announcement, API
(both GraphQL & REST), Auth
, and DataStore
are supported.
These features should also work with other frameworks (such as Nuxt.js & Gatsby), but we trust and rely on your input to make it even better.
The same goes for other features that didn't make it to release today (e.g. Analytics
, Storage
, etc.). Please share SSR-specific use-cases for those categories in a new issue on how Amplify can be better experience, and we'll be happy to help!
With that, I want to personally thank everyone on this issue for your feedback and effort. I'm thrilled that I could play a part in bringing Next.js support to Amplify JS, and want to continue working with y'all to make it even better!
Thanks!
Thank you @ericclemmons and all the team behind this awesome work! ðŠŸ
Hello, where can I find docs that explain SSR setup using Nextjs and Amplify?
@omar-dulaimi https://docs.amplify.aws/start/q/integration/next
Thank you @Danm72
This is awesome but the question is now, how do I use amplifyhosting to enable SSR? :-)
This is awesome but the question is now, how do I use amplifyhosting to enable SSR? :-)
@flyandi That's tracked in this issue: https://github.com/aws-amplify/amplify-console/issues/412
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.
@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.
@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 }
@ohlr Of course! I should've mentioned that or shared this link:
https://docs.amplify.aws/cli/graphql-transformer/auth#public-authorization
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!
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? :/
Wow, I'm just hitting the same issue, @MontoyaAndres! Amazing timing. Out of curiosity, are you using SSO of any kind?
@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...
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