amplify-js icon indicating copy to clipboard operation
amplify-js copied to clipboard

Amplify Causing Errors With Next 12 middleware

Open bluetoken-luke opened this issue 2 years ago • 30 comments

Before opening, please confirm:

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

image

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

bluetoken-luke avatar Nov 02 '21 18:11 bluetoken-luke

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.

image

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

image

image

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.

bluetoken-luke avatar Nov 09 '21 16:11 bluetoken-luke

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

aiji42 avatar Nov 09 '21 16:11 aiji42

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.

bluetoken-luke avatar Nov 10 '21 13:11 bluetoken-luke

@chrisbonifacio any updates over this ?

yogeshwar607 avatar Nov 18 '21 13:11 yogeshwar607

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.

revmischa avatar Jan 03 '22 16:01 revmischa

@chrisbonifacio any updates / solutions found?

zpg6 avatar Jan 03 '22 19:01 zpg6

+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

jacorbello avatar Jan 08 '22 23:01 jacorbello

+1 , being able to use the new NextJS middleware with Amplify Auth without workarounds will be really great.

rodsfreitas avatar Feb 09 '22 22:02 rodsfreitas

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;

dyeoman2 avatar Mar 24 '22 00:03 dyeoman2

Unfortunately, still no updates on this one. We don't officially support Next.js 12 and its new features such as middleware yet.

chrisbonifacio avatar Mar 30 '22 18:03 chrisbonifacio

Is there an update available?

lewisvoncken avatar Apr 28 '22 12:04 lewisvoncken

I am also looking forward to the update.

rnrnstar2 avatar Apr 30 '22 08:04 rnrnstar2

Hi there, any updates? We'd love to use Next 12 and their middleware.

Regards,

Fedeorlandau avatar May 10 '22 09:05 Fedeorlandau

+1

kumar0 avatar May 13 '22 16:05 kumar0

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 avatar May 13 '22 19:05 abdallahshaban557

@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 avatar May 18 '22 10:05 donaldpipowitch

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

abdallahshaban557 avatar May 18 '22 16:05 abdallahshaban557

Thank you very much for your response. I'll do this 🥰

donaldpipowitch avatar May 18 '22 17:05 donaldpipowitch

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.

Sodj avatar May 30 '22 12:05 Sodj

Thank you for the feedback @Sodj! We understand this is an important feature for developers using NextJS v12.

abdallahshaban557 avatar Jun 01 '22 03:06 abdallahshaban557

Any update on this ?!

Mohamadmchawrab avatar Jul 22 '22 08:07 Mohamadmchawrab

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

revmischa avatar Jul 25 '22 22:07 revmischa

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

revmischa avatar Jul 29 '22 00:07 revmischa

@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

revmischa avatar Jul 29 '22 00:07 revmischa

@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 avatar Sep 20 '22 13:09 Amirbahal

@Amirbahal - we are still investigating how to properly fix this issue. Can you please inform us what is your use case for middleware?

abdallahshaban557 avatar Sep 20 '22 17:09 abdallahshaban557

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

revmischa avatar Sep 20 '22 17:09 revmischa

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!

Amirbahal avatar Sep 21 '22 09:09 Amirbahal

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

Amirbahal avatar Sep 21 '22 09:09 Amirbahal

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.

abdallahshaban557 avatar Sep 21 '22 14:09 abdallahshaban557