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
After long time of trying to figure out how to fix it I found a walkaround to avoid calling getSession
directly.
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