There is an issue with the Next.js middleware: [Error: The edge runtime is not compatible with the Node.js 'crypto' module]

Struggling with a problem in next.js and typescript for the past 4 days. If anyone can provide some insight or help with a solution, it would be greatly appreciated. Thank you!

-- This is my middleware.ts

import jwt from "jsonwebtoken";
import { NextResponse, NextRequest } from "next/server";
import clientPromise from "./database/db";

interface RequestWithUserId extends NextRequest {
  userId: string;
}

export async function middleware(req: RequestWithUserId) {
  try {
    const mongoClient = await clientPromise;
    const token = req.cookies.get("jwt")?.value;

    if (!token) {
      return NextResponse.redirect("http://localhost:3000/login");
    }

    const decodedToken: any = jwt.verify(token, process.env.SECRET_KEY);

    const userId = decodedToken._id;

    const checkUserExists = await mongoClient
      .db()
      .collection("user")
      .findOne({ _id: userId });

    if (!checkUserExists) {
      return NextResponse.redirect("http://localhost:3000/login");
    }

    req.userId = userId;
    return NextResponse.next();
  } catch (err) {
    console.log("miidleware error", err);
    NextResponse.redirect("http://localhost:3000/login");
  }
}

export const config = {
  matcher: "/api/addToCart",
};

Answer №1

jsonwebtoken is not compatible with the Edge environment due to its differences from Node.js. A viable alternative would be to utilize jose, which is designed to work with Vercel's Edge Runtime instead of jsonwebtoken.

Answer №2

Success Story | Page Routing

Step One:

npm install jose

Set up middleware.js file in src/middleware.js

import { isAuthenticated } from './lib/jwtTokenControl'

// Configure the middleware for paths starting with `/api/`
export const config = {
  matcher: '/api/v1/:function*'
}

export async function middleware(request) {
  const result = await isAuthenticated(request)

  if (!result) {
    return Response.json({ success: false, message: 'Invalid token. Paths starting with `/api/v1/`' }, { status: 401 })
  }
}

Create jwtTokenControl.js file inside src/lib directory

import * as jose from 'jose'

const jwtConfig = {
  secret: new TextEncoder().encode(process.env.NEXT_PUBLIC_JWT_SECRET),
}

export const isAuthenticated = async req => {
  let token = req.headers.get('authorization') || req.headers.get('Authorization')

  if (token) {
    try {
      if (token.startsWith('Bearer')) {
        token = token.replace('Bearer ', '')
      }

      const decoded = await jose.jwtVerify(token, jwtConfig.secret)

      if (decoded.payload?._id) {
        return true
      } else {
        return false
      }
    } catch (err) {
      console.error('isAuthenticated error: ', err)

      return false
    }
  } else {
    return false
  }
}

Answer №3

Building upon the insights provided by @Nijat, I found a solid foundation to work with.

If you are utilizing Next-Auth, here is my approach to integrating their exposed functionalities. The key components here are the middleware and the use of decode function that leverages jose.jwtVerify. To completely eliminate jsonwebtoken, I made modifications to the encode function to utilize jose.SignJWT instead.

src/middleware.ts:

import { NextRequestWithAuth, withAuth } from 'next-auth/middleware';
import { NextFetchEvent } from 'next/server';
import jwtConfig from './config/jwt';

export default withAuth(
  function middleware(request: NextRequestWithAuth, event: NextFetchEvent) {},
  {
    jwt: {
      decode: jwtConfig.decode,
    },
    callbacks: {
      authorized: ({ req, token }) => {
        console.log(token);
        return !!token;
      },
    },
  }
);

export const config = {
  matcher: [
    '/((?!api/auth|auth|static|_next/static|_next/image|images|favicon.ico|health).*)', // blocking all api routes except /api/auth
  ],
};

src/config/jwt.ts

import { JWT, JWTDecodeParams, JWTEncodeParams, getToken } from 'next-auth/jwt';
import * as jose from 'jose';

const encodedSecret = new TextEncoder().encode(process.env.NEXTAUTH_SECRET);

const encode = async (params: JWTEncodeParams): Promise<string> => {
  const signedToken = await new jose.SignJWT(params.token)
    .setProtectedHeader({ alg: 'HS256' })
    .sign(encodedSecret);
  if (!signedToken) {
    throw new Error('Failed to sign token');
  }
  return signedToken;
};

const decode = async (params: JWTDecodeParams): Promise<JWT | null> => {
  if (!params.token) {
    throw new Error('Failed to verify token');
  }

  let token = params.token;

  if (params.token.startsWith('Bearer')) {
    token = params.token.replace('Bearer ', '');
  }

  try {
    const decoded = await jose.jwtVerify(token, jwtConfig.encodedSecret);

    if (!decoded.payload) {
      throw new Error('Failed to verify token');
    }

    return decoded.payload;
  } catch (error) {
    console.log(error);
    throw new Error(`${error}`);
  }
};

export const jwtConfig = {
  encode,
  decode,
};

export default jwtConfig;

Finally, in src/pages/api/auth/[...nextauth].ts:

import { JWT, JWTDecodeParams, JWTEncodeParams, getToken } from 'next-auth/jwt';
import * as jose from 'jose';

const encodedSecret = new TextEncoder().encode(process.env.NEXTAUTH_SECRET);
const encode = async (params: JWTEncodeParams): Promise<string> => {
  const signedToken = await new jose.SignJWT(params.token)
    .setProtectedHeader({ alg: 'HS256' })
    .sign(encodedSecret);
  if (!signedToken) {
    throw new Error('Failed to sign token');
  }
  return signedToken;
};

const decode = async (params: JWTDecodeParams): Promise<JWT | null> => {
  if (!params.token) {
    throw new Error('Failed to verify token');
  }

  let token = params.token;

  if (params.token.startsWith('Bearer')) {
    token = params.token.replace('Bearer ', '');
  }

  try {
    const decoded = await jose.jwtVerify(token, jwtConfig.encodedSecret);

    if (!decoded.payload) {
      throw new Error('Failed to verify token');
    }

    return decoded.payload;
  } catch (error) {
    console.log(error);
    throw new Error(`${error}`);
  }
};

export const jwtConfig = {
  encode,
  decode,
  encodedSecret,
};

export default jwtConfig;

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A guide to resolving the error "Cannot read properties of undefined (reading 'length')" while implementing pagination with react, Material-UI, and TypeScript

As I work on my code, my goal is to display a limited number of cards per page based on the data stored in a JSON file. I anticipated that clicking on the ">" or "<" icon would navigate to the next or previous page respectively, updating the displayed card ...

Using FormData in conjunction with a NextJS API

I am in the process of developing a basic CRUD application using NextJS in combination with react-redux. The main function of this application is to store contact information for users. When attempting to add a new contact, I encounter an issue with sendin ...

Material UI React Autocomplete Component

I'm currently working on integrating an Autocomplete component using the Material UI library. However, I've encountered a challenge - I'm unsure of how to properly pass the value and onChange functions, especially since I have a custom Text ...

Passing data from ModalService to a component

Currently, I am attempting to utilize the ngx-bootstrap-modal in order to transfer data from a modal service to a modal component. While reviewing the examples, it is suggested to use the following code: this.modalService.show(ModalContentComponent, {init ...

Are there more effective methods for handling the scenario where a component returns null?

Any suggestions for a more efficient approach to this scenario (where a component returns null)? Within my Page component, I am required to update the pageTitle state which is then displayed in a component higher up in the component tree. layout.tsx: imp ...

The compilation of TypeScript and ES Modules is not supported in Firebase Functions

Recently, I integrated Firebase Functions into my project with the default settings, except for changing the value "main": "src/index.ts" in the package.json file because the default path was incorrect. Here is the code that was working: // index.ts cons ...

Updating a string's value in Angular based on user input

I am currently developing a custom offer letter template that will dynamically update key data points such as Name, Address, Role, Salary, etc based on the selected candidate from a list. The dynamic data points will be enclosed within <<>> in ...

Generating directory for application, only to find TypeScript files instead of JavaScript

While following a tutorial on setting up a react.js + tailwindcss app, I used the command npx create-next-app -e with-tailwindcss [app name]. However, instead of getting javascript files like index.js, I ended up with TypeScript files like index.tsx. You c ...

Creating JSX elements in React by mapping over an object's properties

I need to display the properties of an object named tour in JSX elements using React without repeating code. Each property should be shown within a div, with the property name as a label and its value next to it. Although I attempted to iterate over the o ...

Utilizing the split function within an ngIf statement in Angular

<div *ngIf="store[obj?.FundCode + obj?.PayWith].status == 'fail'">test</div> The method above is being utilized to combine two strings in order to map an array. It functions correctly, however, when attempting to incorporate the spli ...

Unusual conduct exhibited by a 16th version Angular module?

I've created a unique component using Angular 16. It's responsible for displaying a red div with a message inside. import { ChangeDetectionStrategy, Component, Input, OnInit, } from "@angular/core"; import { MaintenanceMessage } ...

After submitting a multi-image form from Angular, the "req" variable is not defined

I'm currently facing an issue with submitting a form from Angular 7 to a Node backend using Multer as middleware and Express.json() as bodyParser. While the text data is successfully transmitted to the backend, the image fields are appearing empty {}. ...

Guide on how to create a custom response using class-validator in NestJS

Is it feasible to customize the error response generated by class-validator in NestJs? The default error message structure in NestJS looks like this: { "statusCode": 400, "error": "Bad Request", "message": [ { "target": {} ...

Having difficulty ensuring DayJs is accessible for all Cypress tests

Currently embarking on a new Cypress project, I find myself dealing with an application heavily focused on calendars, requiring frequent manipulations of dates. I'm facing an issue where I need to make DayJs globally available throughout the entire p ...

Are you looking for a streamlined method to create styled components with MaterialUI?

I am exploring ways to easily define convenient components in my application utilizing Material-UI. After referring to an example from the Material-UI documentation for Appbar, I attempted to incorporate some React components for better readability. My c ...

How to dynamically disable a checkbox in Angular reactive forms depending on the value of another checkbox

I am attempting to deactivate the checkbox based on the value of other checkboxes. Only one of them can be activated at a time. When trying to activate one of the checkboxes, I encounter an issue where the subscribed value is being repeated multiple times ...

Using a static value in the comparator is necessary for Array.find to function properly in Typescript

Looking to retrieve an item from an array: const device = this.selectedDevtype.devices.find(item => console.log(this.deviceID); return item.device_id === this.deviceID; }); console.log(device); When this.deviceID is logged, it shows "4", but t ...

Inversify: class-based contextual dependency injection

I am currently experimenting with injecting loggers into various classes using inversify. My goal is to pass the target class name to the logger for categorization. The challenge I'm facing is the inability to access the target name from where I am c ...

The function cannot be accessed during the unit test

I have just created a new project in VueJS and incorporated TypeScript into it. Below is my component along with some testing methods: <template> <div></div> </template> <script lang="ts"> import { Component, Vue } from ...

Is the TypeScript compiler neglecting the tsconfig.json file?

I am new to TypeScript and currently exploring how to set it up in WebStorm. One of the first steps I took was creating a tsconfig.json file in the main directory of my project and updating the built-in TypeScript compiler to version 1.6.2. However, despit ...