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

What's the best way to integrate redux-persist into a TypeScript project?

Having some difficulty adding redux-persist to my React project (in typescript). The compilation is failing with the following error message: Could not find a declaration file for module 'redux-persist/lib/storage'. '.../WebstormProjects/c ...

Utilize the class type of a method parameter as the method type for another parameter

Here's a quick example illustrating my desired functionality. // Every time, the ACL class will have a different name such as "UsersACL", etc. export class EventsACL { test(): { read: true, write: true } { } } // This function acts ...

Difficulty Resolving Parameter Resolution in Angular 5 Shared Library Module ([object Object], ?, ?)

I'm facing an issue while attempting to integrate a custom shared component library into my Angular application, which has been upgraded from Angular 5 to Angular 4. Unfortunately, I am unable to resolve the problem at hand. The error message I&apos ...

Unveiling the Ultimate Method to Package Angular 2 Application using SystemJS and SystemJS-Builder

I'm currently in the process of developing an application and I am faced with a challenge of optimizing the performance of Angular 2 by improving the loading speed of all the scripts. However, I have encountered an error that is hindering my progress: ...

Adding items to the array is only effective when done within the loop

My approach involves retrieving data from an API using axios, organizing it within a function named "RefractorData()," and then pushing it onto an existing array. However, I have encountered a problem where the array gets populated within a forEach loop, a ...

The requirement for the type 'Promise<QuerySnapshot<DocumentData>>' is that it must include a method '[Symbol.iterator]()' to provide an iterator

Currently, I am developing a Firebase project with NextJS and Typescript. My goal is to retrieve data from the Firestore database; however, I encountered an error message stating Type 'Promise<QuerySnapshot<DocumentData>>' must have a ...

Angular Nested Interface is a concept that involves defining an

Looking for guidance on creating a nested interface for JSON data like this: Any help is appreciated. JSON Structure "toto": { "toto1": [], "toto2": [], "toto3": [], } Interface Definition export interface Itot ...

Typescript is facing an issue locating the declaration file

I'm encountering an issue with TypeScript not recognizing my declaration file, even though it exists. Can anyone provide insight into why this might be happening? Here is the structure of my project: scr - main.ts - dec.d.ts str-utils - index. ...

Service error: The function of "method" is not valid

In one of my Angular 2 applications, I have a class that contains numerous methods for managing authentication. One particular method is responsible for handling errors thrown by the angular/http module. For example, if a response returns a status code o ...

Is there a possibility of Typescript expressions `A` existing where the concept of truthiness is not the same as when applying `!!A`?

When working with JavaScript, it is important to note that almost all expressions have a "truthiness" value. This means that if you use an expression in a statement that expects a boolean, it will be evaluated as a boolean equivalent. For example: let a = ...

Developing an Angular 2 Cordova plugin

Currently, I am in the process of developing a Cordova plugin for Ionic 2. The plugin is supposed to retrieve data from an Android device and display it either on the console or as an alert. However, I am facing difficulty in displaying this data on the HT ...

Every time stripe.checkout.sessions.listLineItems is called, it returns a promise that is waiting to be fulfilled

My current challenge involves retrieving data from Firebase and using the ID to fetch items from Stripe Checkout. Unfortunately, every time I attempt this process, I end up with a pending promise. const colRef = collection(db, `users/${session.user.email}/ ...

Using Angular to bind a click event to an element after it has been compiled

I am currently developing an application for students using Angular 5. In this application, users can access and view various documents. When a user enters a document, a set of tools, including a marker tool, are displayed. This marker tool allows users to ...

Best practices for implementing "Event Sourcing" in the NestJS CQRS recipe

I've been exploring the best practices for implementing "Event Sourcing" with the NestJS CQRS recipe (https://docs.nestjs.com/recipes/cqrs). After spending time delving into the features of NestJS, I have found it to be a fantastic framework overall. ...

Tips for designing unique 404 error pages with NextJS

Is there a way to create a custom layout for my 404 error page that is different from the main layout? I'm facing an issue where components like the footer or navigation buttons from the main layout are appearing on my custom 404 layout. Here's ...

Restricting HTTP requests to once every 200 milliseconds in Angular 4 with filtering in place

In my current project, I am working on a page that utilizes an ngFor to display an array of objects. One of the features I want to implement is the ability for users to filter these objects by entering specific keywords in an input field. Since the data f ...

Guide to retriecing a state in Next.js 14

Check out my code below: "useState" // firebase.js import firebase from "firebase/app"; import "firebase/auth"; // Import the authentication module export default async function handler(req, res) { if (req.method !== " ...

Designate a reference to a specific element within a React component

I have been attempting to incorporate react-signature-canvas into my Web App. The API methods necessitate a ref to the SignatureCanvas, as demonstrated in this example. I am endeavoring to achieve the same outcome within my NextJS app with the following co ...

Is it possible to execute a system command within an Ionic3 application?

How can I run a command from an app running in Chromium on Linux (or potentially Windows or Android in the future)? Why do you want to do this? To control, for example, some audio/TV equipment using cec-client. echo "tx 20:36" | cec-client RPI -s -d 4 ...

Ensure that the key and value types in a Typescript Map are strictly specified

Is it feasible to generate a map that consists of key-value pairs, where the key is represented by a string and the value corresponds to an object containing specific properties defined by mapValue? type mapValue { first: string; second: boolean; } Yo ...