Reputation: 11
@ Using NextJS 13.3 with App Dir and API Routes.
I am trying to build a auth system with my external backend nodeJS. Basically, front send credentials to backend, backend validate and return a accessToken and refreshToken. Then, NextJS create a new JWT and encode the the user info, token and refreshToken sent from backend.
To validate the session, the middleware fetch the api route (in nextjs), where decode the JWT to check if the accessToken from backend is valid, if expired, then try refresh a new accessToken using the refreshToken, after receive the new accessToken, api set the cookies and return the success=true if success is true, then middleware proceed, if false, then redirect to login page.
That's working like a charm.. however, There's a problem.
When Backend send the new accessToken to NextJS, the nextjs API set the cookies, but looks like the cookies are set only after middleware response. in short, when user visit the page after refresh the token, give a error forbidden because still is the old cookie. if refresh the page again, then the session como with new cookies.
Someone can help me fix it and try find where i am wrong?
Middleware:
import { NextResponse } from 'next/server';
import conf from '@/config/conf';
export async function middleware(request) {
const accessToken = request.cookies.get('accessToken')?.value;
const verifySessionResponse = await fetch(`${conf.BASE_URL}/proxy/auth/verify-session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ accessToken: accessToken })
});
const json = await verifySessionResponse.json();
if (!json.success) {
return NextResponse.rewrite(new URL('/auth/login', request.url))
}
return NextResponse.next(verifySessionResponse);
}
export const config = {
matcher: '/dashboard/:path*'
}
Route API:
import conf from '@/config/conf';
import jwt from 'jsonwebtoken';
export async function POST(request) {
const { accessToken } = await request.json();
if (!accessToken) {
return new Response(JSON.stringify({ success: false, message: 'No access token provided' }), { status: 401 });
}
try {
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET);
const refreshToken = decoded.access.refreshToken.token;
const tokenExpires = decoded.access.accessToken.expiresIn;
const refreshTokenExpires = decoded.access.refreshToken.expiresIn;
if (Date.now() > refreshTokenExpires) {
return new Response(JSON.stringify({ success: false, message: 'Refresh token expired' }), { status: 401 });
}
if (Date.now() > tokenExpires) {
try {
const refresh = await fetch(`${conf.API_URL}/auth/refresh-token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'authorization': `Bearer ${refreshToken}`
}
});
const refreshJson = await refresh.json();
if (!refreshJson.success) {
return new Response(JSON.stringify({ success: false, message: refreshJson.message }), { status: 401 });
}
const payout = {
id: decoded.id,
email: decoded.email,
firstName: decoded.firstName,
lastName: decoded.lastName,
role: decoded.role,
access: {
accessToken: {
token: refreshJson.cookies.accessToken,
expiresIn: refreshJson.cookies.accessTokenExpires
},
refreshToken: {
token: refreshJson.cookies.refreshToken,
expiresIn: refreshJson.cookies.refreshTokenExpires,
}
}
};
const newToken = jwt.sign(payout, process.env.JWT_SECRET, { expiresIn: '2d' });
const tokenExpiresDate = new Date(Date.now() + conf.COOKIES_EXPIRES * 1000); // 2 Days
console.log('Token refreshed');
const response = new Response(JSON.stringify({ success: true, message: refreshJson.message, token: newToken, expiresIn: tokenExpiresDate }), {
status: 200
});
response.headers.set('set-cookie', `accessToken=${newToken}; Path=/; HttpOnly; SameSite=Lax; Expires=${tokenExpiresDate.toUTCString()}`);
return response;
}
catch (err) {
console.log(err);
return new Response(JSON.stringify({ success: false }), { status: 401 });
}
}
return new Response(JSON.stringify({ success: true }), { status: 200 });
} catch (err) {
return new Response(JSON.stringify({ success: false }), { status: 401 });
}
}
I already tried everything! I just want the API set the cookies after receive the new token, and then proceed.
Upvotes: 1
Views: 1683
Reputation: 1
You can easily create a new object of the NextResponse class and set cookies in it. Here's an example of the response function I use
export function jsonResponse(data: object, status = 200): any {
let cookies = undefined;
if (typeof data == 'object' && 'cookies' in data) {
cookies = data.cookies;
delete data.cookies;
}
let body = '';
try {
body = JSON.stringify(data);
} catch (error) {
body = JSON.stringify(ErrorHandler(error, __filename, 500, false));
}
const customResponse = new NextResponse(body, {
status, // Set your desired status code here
headers: {
'Content-Type': 'application/json', // Set the content type if needed
},
});
if (Array.isArray(cookies)) {
for (const cookie of cookies) {
customResponse.cookies.set(cookie);
}
} else {
cookies && customResponse.cookies.set(cookies as any);
}
return customResponse;
}
Upvotes: 0