Search code examples
reactjstypescriptnext.jsoauthnext-auth

getToken occurs error Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime


I'm creating a project which uses NextAuth.JS as authentication library and using Redux-Saga as state manager.

In order to implement refresh token rotation I created following functions:

get-token.ts:

import { UUID } from 'crypto';
import jwt from 'jsonwebtoken';
import { Session } from 'next-auth';

import { refreshToken } from './refresh-token';

export const getToken = async (session?: Session) => {
   if (!session) return null;

   const accessToken = session.accessToken;

   if (!accessToken) return null;

   const accessTokenPayload = jwt.decode(accessToken!) as { id: UUID; exp: number };
   if (accessTokenPayload.exp && Date.now() > accessTokenPayload.exp) {
      const refreshedAccessToken = await refreshToken(session);

      if (!refreshedAccessToken) return accessToken;

      return refreshedAccessToken;
   }

   return accessToken;
};

refresh-token.ts:

import { Session } from 'next-auth';

export const refreshToken = async (session: Session) => {
   if (!session) return;

   const refreshToken = session.refreshToken;

   if (!refreshToken) return;

   const response = await fetch('http://localhost:3001/admins/auth/refresh', {
      method: 'POST',
      headers: {
         'Content-Type': 'application/json',
         Authorization: `Bearer ${refreshToken}`,
      },
   });

   if (response.ok) {
      const data: TTokens = await response.json();

      session!.accessToken = data.accessToken;

      return data.accessToken;
   }
};

In order not to take session as a prop for a function I used to define session inside of these functions as

import { getSession } from 'next-auth/react';
import { Session } from 'next-auth';

...

const session = await getSession()

but each time I received error:

./node_modules/@babel/runtime/regenerator/index.js
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation

Import trace for requested module:
./node_modules/@babel/runtime/regenerator/index.js       
./node_modules/next-auth/react/index.js
./src/store/sagas/admins.saga.ts
./src/store/sagas/index.ts
./src/store/index.ts
./src/components/workspace/Tabs/products/index.tsx       
./src/const/tabs.ts
./src/const/index.ts

I thought maybe it's something with a function so I decided to move session out of this function and make it as a prop..

I'm using getToken in saga functions before each request, f.e.: admins.saga.ts:

function* fetchAdminsSaga(): Generator<unknown> {
   const session = yield call(getSession);

   try {
      const token = yield call(async () => await getToken(session as Session));

      const response = yield call(() =>
         fetch('http://localhost:3001/admins', {
            headers: {
               Authorization: `Bearer ${token}`,
            },
         }),
      );
...

But the problem remains as I use same getSession here.

Official NextAuth docs for getSession tells us that:

getSession()
Client Side: Yes
Server Side: No (See: getServerSession()

I considered to either save accessToken which I actually need in localStorage but it's bad idea as it will be easily exposed. I even considered to save accessToken in redux storage but no real difference with previous variant as I'm using Redux-Persist which saves all the data in the same localStorage. So the only way to save accessToken more or less save - use nextauth session object (as


Solution

  • After long time of trying to figure out how to fix it I found a walkaround to avoid calling getSession directly.

    Solution

    1. Change const session = yield call(getSession) to:

    const response = yield call(fetch, '/api/auth/session');
    const session = yield call(() => (response as Response).json());
    

    2. Define session as api route handler:

    import { NextApiRequest, NextApiResponse } from 'next';
    import { NextRequest, NextResponse } from 'next/server';
    import { getServerSession } from 'next-auth';
    import { options } from '../[...nextauth]/options';
    
    export async function GET(req: NextRequest, res: NextResponse) {
       const session = await getServerSession(
          req as unknown as NextApiRequest,
          {
             ...res,
             getHeader: (name: string) => res.headers?.get(name),
             setHeader: (name: string, value: string) => res.headers?.set(name, value),
          } as unknown as NextApiResponse,
          options,
       );
    
       return NextResponse.json(session);
    }
    

    Had some troubles with getServerSession with error such as TypeError: res.getHeader is not a function but this SO solution helped me fix it