amplify-js
amplify-js copied to clipboard
Amplify Causing Errors With Next 12 middleware
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
- [X] I have read the guide for submitting bug reports.
- [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
Next.js
Amplify APIs
Authentication
Amplify Categories
auth
Environment information
# Put output below this line
Describe the bug
We are working on updating our application up to NextJS 12. In doing so we are interested in moving the authentication check for protected pages to the new middleware feature available in next 12 (link). This code runs on the server before a request is completed. With that in mind, I went about attempting to use withSSRContext to check the users authentication status in the middleware.
import type { NextFetchEvent, NextRequest } from 'next/server';
import { Amplify, withSSRContext } from 'aws-amplify';
import { AwsConfig } from '../../../../../../src/config';
Amplify.configure({ ...AwsConfig, ssr: true });
export const middleware = async (req: NextRequest, ev: NextFetchEvent) => {
console.log('req', req);
const { Auth } = withSSRContext({ req: req });
return new Response('Hello World!');
};
As you can see above, I didn't get too far before this already broke the app. It seems that something underlying is attempting to use window which of course is not present on server. This looks to be happening in Reachablility
Expected behavior
Authentication is checked in the same manner it would be for getServerSideProps
Reproduction steps
In a next 12 application. place a _middleware.ts or _middleware.js file in one of your page directories. In that middleware file, simply import Amplify and that should be enough to draw out the exception.
Code Snippet
import type { NextFetchEvent, NextRequest } from 'next/server';
import { Amplify, withSSRContext } from 'aws-amplify';
import { AwsConfig } from '../../../../../../src/config';
Amplify.configure({ ...AwsConfig, ssr: true });
export const middleware = async (req: NextRequest, ev: NextFetchEvent) => {
console.log('req', req);
const { Auth } = withSSRContext({ req: req });
return new Response('Hello World!');
};
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
So been digging into this as we badly want this feature. What I have found is that in the networkMonitor in Reachability there is a check for if this is running in node (which being on the server it should be). However, that check is coming back false. This allows the networkMonitor to continue on in its code thinking presumably its in a browser. isWebWorker() winds up returning false which makes the globalObj window which then doesn't exist on the server.
So went looking for why this isNode call is coming back false. Looking at browserOrNode() it is checking the node version in process in order to determine if this is node on the server. However process.versions is empty when running the middleware (it is not when running gSSP).
This seems to be the cause of the window undefined error seen above when using amplify in nextjs middleware. Im uncertain on if the fix should be on amplify's end or next's. It feels like next should probably be setting that version. But unsure. Im doing all I can to try and work around this without having to fork amplify and make changes but without being able to modify whats in process, its looking bleak.
Very interesting. I ran into the same problem and couldn't understand why the function for node required a window, but your pursuit has cleared up that question for me.
The sandbox for running Next.js middleware is quite limited in its functionality (probably to ensure the performance of running Edge), so I hope that amplify will change its implementation to one that does not depend on process.versions.
Incidentally, I temporarily work around this problem by decoding the token (JWT) on the cookie directly.
https://github.com/aiji42/next-fortress/blob/main/src/cognito.ts https://github.com/aiji42/next-fortress#control-by-amazon-cognito
Very interesting. I ran into the same problem and couldn't understand why the function for node required a window, but your pursuit has cleared up that question for me.
The sandbox for running Next.js middleware is quite limited in its functionality (probably to ensure the performance of running Edge), so I hope that amplify will change its implementation to one that does not depend on process.versions.
Incidentally, I temporarily work around this problem by decoding the token (JWT) on the cookie directly.
https://github.com/aiji42/next-fortress/blob/main/src/cognito.ts https://github.com/aiji42/next-fortress#control-by-amazon-cognito
Thank you for this. It helped become the base of us being able to work around this for now.
@chrisbonifacio any updates over this ?
I am experiencing this issue as well.
I was able to get withSSRContext
working fine in getServerSideProps
but I can't use it with middleware because of no window
.
@chrisbonifacio any updates / solutions found?
+1, would like to see a solution here. At the moment, I'm toying with the idea of saving the id token to a cookie upon login and parsing the token in the middleware. Seems like a hacky alternative to the solution provided in this document.
Instead, I get this:
event - compiled client and server successfully in 292 ms (3660 modules)
wait - compiling /portal...
event - compiled client and server successfully in 253 ms (3668 modules)
error - node_modules/@aws-amplify/core/lib-esm/Util/Reachability.js (21:0) @ <unknown>
ReferenceError: window is not defined
+1 , being able to use the new NextJS middleware with Amplify Auth without workarounds will be really great.
I had the same problem and next-fortress didn't quite do that trick for me, but I was able to take some of their code to create the solution below.
// _middleware.js
import { NextResponse } from 'next/server';
import { decodeProtectedHeader, importJWK, jwtVerify } from 'jose';
// Middleware that prevents unauthenticated users from accessing protected resources
async function handler(req) {
const url = req.nextUrl.clone();
try {
// Define paths that don't require authentication
const unauthenticatedPaths = [
'/',
'/login',
'/password-reset',
'/pricing',
'/signup',
'/support',
];
// Allow users to continue for pages that don't require authentication
if (unauthenticatedPaths.includes(url.pathname)) {
return NextResponse.next();
} else {
// Authenticate users for protected resources
// Cognito data
const region = process.env.AWS_REGION;
const poolId = process.env.AWS_COGNITO_USER_POOL_ID;
const clientId = process.env.AWS_USER_POOLS_WEB_CLIENT_ID;
// Get the user's token
const token = Object.entries(req.cookies).find(([key]) =>
new RegExp(
`CognitoIdentityServiceProvider\\.${clientId}\\..+\\.idToken`
).test(key)
)?.[1];
if (token) {
// Get keys from AWS
const { keys } = await fetch(
`https://cognito-idp.${region}.amazonaws.com/${poolId}/.well-known/jwks.json`
).then((res) => res.json());
// Decode the user's token
const { kid } = decodeProtectedHeader(token);
// Find the user's decoded token in the Cognito keys
const jwk = keys.find((key) => key.kid === kid);
if (jwk) {
// Import JWT using the JWK
const jwtImport = await importJWK(jwk);
// Verify the users JWT
const jwtVerified = await jwtVerify(token, jwtImport)
.then((res) => res.payload.email_verified)
.catch(() => failedToAuthenticate);
// Allow verified users to continue
if (jwtVerified) return NextResponse.next();
}
}
}
} catch (err) {
console.log('err', err);
}
// Send 401 when an unauthenticated user trys to access API endpoints
if (url.pathname.includes('api')) {
return new Response('Auth required', { status: 401 });
} else {
// Redirect unauthenticated users to the login page when they attempt to access protected pages
return NextResponse.redirect(`${url.origin}/login`);
}
}
export default handler;
Unfortunately, still no updates on this one. We don't officially support Next.js 12 and its new features such as middleware yet.
Is there an update available?
I am also looking forward to the update.
Hi there, any updates? We'd love to use Next 12 and their middleware.
Regards,
+1
Hello everyone, we are currently exploring what it would take for our Auth category for Amplify to function consistently with NextJS 12 Middleware. We will update this issue when we have more feedback!
@abdallahshaban557, thanks for the update. So this means the Amplify team is looking into officially supporting NextJS 12? Or are you just looking at the "Auth in Middleware" part right now? Just asking, because there is also this issue which includes more blockers as far as I know: https://github.com/aws-amplify/amplify-hosting/issues/2343 Does the AWS/Amplify internally discuss how to proceed with Next.js support and updates in the future? I'd be really nice to have a bit more clarification here (e.g. it would also be nice to use NextJS 12 support sooner for people who don't need middleware; what will be the strategy for v13; will there ever be an AWS-official CDK construct, etc.).
@donaldpipowitch - these are exactly the types of conversation we are having internally! I would love to have a conversation with you to dive deeper into the different features of NextJS that we should support. Can you please send me an email to [email protected] so that I can setup time for us to discuss this further?
Thank you very much for your response. I'll do this 🥰
Middleware feature is a must have! I can't afford to put redirect code in every single page And I can't afford to put the redirect code in client side
I hope this gets supported soon.
Thank you for the feedback @Sodj! We understand this is an important feature for developers using NextJS v12.
Any update on this ?!
Using Next 12.2.3
Using withSSRContext
in middleware:
export const middleware: NextMiddleware = async (req, evt) => {
const { Auth } = withSSRContext({ req })
let data
let user
try {
user = await Auth.currentAuthenticatedUser()
console.log("user is authenticated")
console.log(user)
} catch (err) {
console.log("error: no authenticated user")
return redirecter(req, evt)
}
}
I am getting the following error:
error - Error: The edge runtime does not support Node.js 'buffer' module.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
at <unknown> (webpack-internal:///(middleware)/../../node_modules/next/dist/server/web/adapter.js:156)
at Object.get (webpack-internal:///(middleware)/../../node_modules/next/dist/server/web/adapter.js:156:19)
at eval (webpack-internal:///(middleware)/../../node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-crypto/util/build/convertToBuffer.js:9:56)
at Object.(middleware)/../../node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-crypto/util/build/convertToBuffer.js (evalmachine.<anonymous>:7245:1)
at __webpack_require__ (evalmachine.<anonymous>:37:33)
at fn (evalmachine.<anonymous>:281:21)
at eval (webpack-internal:///(middleware)/../../node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-crypto/util/build/index.js:6:25)
at Object.(middleware)/../../node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-crypto/util/build/index.js (evalmachine.<anonymous>:7256:1)
at __webpack_require__ (evalmachine.<anonymous>:37:33)
at fn (evalmachine.<anonymous>:281:21)
at eval (webpack-internal:///(middleware)/../../node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-crypto/sha256-browser/build/webCryptoSha256.js:4:14) {
middleware: true
✗ npm ls @aws-sdk/client-cloudwatch-logs
[email protected] /Users/cyber/dev/platform
└─┬ [email protected] -> ./packages/frontend
└─┬ @aws-amplify/[email protected]
└── @aws-sdk/[email protected]
It appears client-cloudwatch-logs uses buffer
from Nodejs which vercel presumably does not support in their edge function API
Another error I get:
info - Creating an optimized production build
Failed to compile.
../../node_modules/@aws-amplify/core/lib-esm/JS.js
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Import trace for requested module:
../../node_modules/@aws-amplify/core/lib-esm/index.js
../../node_modules/aws-amplify/lib-esm/index.js
./src/middleware.ts
../../node_modules/@aws-amplify/datastore/node_modules/immer/dist/immer.esm.js
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Import trace for requested module:
../../node_modules/@aws-amplify/datastore/lib-esm/util.js
../../node_modules/@aws-amplify/datastore/lib-esm/index.js
../../node_modules/aws-amplify/lib-esm/index.js
./src/middleware.ts
../../node_modules/amazon-cognito-identity-js/es/Client.js
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Import trace for requested module:
../../node_modules/amazon-cognito-identity-js/es/CognitoUserPool.js
../../node_modules/amazon-cognito-identity-js/es/index.js
../../node_modules/@aws-amplify/auth/lib-esm/index.js
../../node_modules/aws-amplify/lib-esm/index.js
./src/middleware.ts
../../node_modules/lodash/_root.js
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Import trace for requested module:
../../node_modules/lodash/isBuffer.js
../../node_modules/lodash/isEmpty.js
../../node_modules/@aws-amplify/analytics/lib-esm/Providers/AmazonPersonalizeProvider.js
../../node_modules/@aws-amplify/analytics/lib-esm/Providers/index.js
../../node_modules/@aws-amplify/analytics/lib-esm/index.js
../../node_modules/aws-amplify/lib-esm/index.js
./src/middleware.ts
../../node_modules/@aws-amplify/api-graphql/node_modules/graphql/error/GraphQLError.mjs
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Import trace for requested module:
../../node_modules/@aws-amplify/api-graphql/node_modules/graphql/error/index.mjs
../../node_modules/@aws-amplify/api-graphql/node_modules/graphql/index.mjs
../../node_modules/@aws-amplify/api-graphql/lib-esm/GraphQLAPI.js
../../node_modules/@aws-amplify/api-graphql/lib-esm/index.js
../../node_modules/@aws-amplify/api/lib-esm/index.js
../../node_modules/aws-amplify/lib-esm/index.js
./src/middleware.ts
Next docs: https://nextjs.org/docs/api-reference/edge-runtime
@chrisbonifacio I created a reproduction repo here - just go install deps and build it to see the issue in action
https://github.com/revmischa/amplify-ssr-next-middleware-repro
@chrisbonifacio this would be a life saver feature. Do you guys have a plan to fix it? Would you keep us updated on any website? Web page?
Thanks Amir
@Amirbahal - we are still investigating how to properly fix this issue. Can you please inform us what is your use case for middleware?
I was able to work around this by switching from Amplify to next-auth. Here is my next-auth cognito config: https://github.com/jetbridge/sst-prisma/blob/master/web/pages/api/auth/%5B...nextauth%5D.ts
Thanks a lot @revmischa I really appreciate it. Having a look and I think using next auth is a good idea for my project anyway!
Yes @abdallahshaban557 in a nutshell, my intention was to check the current authenticated user in the middle ware + the path of the request. If the authenticated user has access to the path the request gets fulfilled, else, user gets redirected to a different path. :)
Hi @Amirbahal - gotcha! Thank you for sharing the details of your use case! Makes total sense on why you would want to do that in Middleware. We are currently working on fixing this issue! We will provide feedback on this issue when it is resolved.