Вопрос или проблема
Недавно я отказался от Next Auth и начал реализовывать свою собственную сессию и аутентификацию на основе JWT cookies. У меня сейчас проблемы с файлом middleware в next.js, где мой JWT не обновляется, что бы я ни пробовал. Я могу передать refreshToken на мой endpoint обновления, но data.newAuthToken в моей функции middleware является неопределенным.
import { NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
async function refreshAuthToken(refreshToken) {
const response = await fetch(`${process.env.URL}/api/auth/refresh-token`, {
method: 'POST',
headers: { Authorization: `Bearer ${refreshToken}` },
});
if (response.ok) {
const data = await response.json();
return data.newAuthToken; // НЕОПРЕДЕЛЕНО ЗДЕСЬ
}
return null; // Вернуть null, если обновление не удалось
}
export async function middleware(req) {
const authToken = req.cookies.get('authToken')?.value;
const refreshToken = req.cookies.get('refreshToken')?.value;
if (authToken) {
try {
// Попытка проверить `authToken`
await jwtVerify(authToken, secret);
return NextResponse.next();
} catch (error) {
// Если authToken истек, попытка его обновить
if (error.code === 'ERR_JWT_EXPIRED' && refreshToken) {
const newAuthToken = await refreshAuthToken(refreshToken);
if (newAuthToken) {
// Установить новый `authToken` в cookies и продолжить
const response = NextResponse.next();
response.cookies.set('authToken', newAuthToken, { httpOnly: true, path: "https://stackoverflow.com/" });
return response;
}
}
}
}
// Перенаправить на страницу входа, если нет действительного auth token или обновление не удалось
return NextResponse.redirect(new URL("https://stackoverflow.com/", req.url));
}
// Конфигурация маршрутизатора для применения middleware только к определенным маршрутам
export const config = {
matcher: [
'/my-account',
'/messages'
],
};
Вот мой endpoint обновления:
import { jwtVerify, SignJWT } from 'jose';
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { connectToDB } from '@utils/database';
import User from '@models/User';
import { headers } from 'next/headers'; // Импортировать заголовки, чтобы получить доступ к заголовку Authorization
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
//api/auth/refresh-token
export async function POST() {
try {
// Извлечь refresh token из cookies
var refreshToken = cookies().get('refreshToken')?.value;
if (!refreshToken) {
// ОБРАБОТАТЬ ДАННЫЕ, ПЕРЕДАННЫЕ ИЗ MIDDLEWARE
const authHeader = headers().get('Authorization');
refreshToken = authHeader?.split(' ')[1]; // Предполагая формат "Bearer <token>"
if(!refreshToken) {
return new Response("Unauthorized", { status: 401 });
}
}
// Проверить refresh token
const { payload } = await jwtVerify(refreshToken, secret);
const id = payload.userId;
// Опционально, проверить базу данных, если нужна более актуальная информация
await connectToDB();
const user = await User.findById(id);
if (!user) {
return new Response("User not found", { status: 404 });
}
// Создать новый токен доступа с актуальной информацией о пользователе
const newAuthToken = await new SignJWT({
id: user._id.toString(),
username: user.username,
image: user.image,
admin: user.admin,
blocked: user.blocked,
email_verified: user.email_verified,
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('1h') // Установить срок действия для токена доступа
.sign(secret);
// Опционально, обновить refresh token, если вы хотите продлить его срок жизни
const newRefreshToken = await new SignJWT({ userId: user._id.toString() })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('30d') // Установить срок действия refresh token
.sign(secret);
// Создать NextResponse и установить новые токены в HTTP-only cookies
const response = NextResponse.json({ message: 'Token refreshed successfully' });
response.cookies.set('authToken', newAuthToken, { httpOnly: true, path: "https://stackoverflow.com/" });
response.cookies.set('refreshToken', newRefreshToken, { httpOnly: true, path: "https://stackoverflow.com/" });
return response;
} catch (error) {
console.error("Ошибка при обновлении токена:", error);
return new Response(JSON.stringify({ error: 'Server error' }), { status: 500 });
}
}
Ответ или решение
Обновление JWT с помощью промежуточного программного обеспечения Next.js через куки
При создании системы аутентификации с использованием JWT и куки в приложении на Next.js может возникнуть множество сложностей. В данном случае вы сталкиваетесь с проблемой обновления токена JWT в промежуточном программном обеспечении, где ценная информация (новый токен аутентификации) оказывается недоступной. Ниже вы найдете подробный анализ вашего кода и рекомендации по устранению ошибок.
Проблема с получением нового токена
Исходная проблема заключается в том, что переменная data.newAuthToken
в функции refreshAuthToken
возвращает undefined
. Это может произойти по нескольким причинам:
- Некорректный endpoint или ошибка сервера: Убедитесь, что ваш endpoint для обновления токена настроен правильно и возвращает необходимые данные.
- Неправильная форма ответа: Убедитесь, что ваш endpoint возвращает JSON в правильном формате. Вам следует проверить, что возвращаемая структура данных соответствует
data.newAuthToken
.
// Проверьте что ваше API действительно возвращает данные в нужной форме
const data = await response.json();
console.log(data); // Это поможет увидеть, что возвращает ваш сервер
return data.newAuthToken;
- Обработка ошибок: Убедитесь, что ваш серверный код корректно обрабатывает все возможные ошибки и всегда возвращает JSON-ответ с соответствующей структурой.
Корректировка кода Middleware
Рассмотрим ваш код для промежуточного программного обеспечения и предложим несколько исправлений. Ваша функция middleware
должна проверять наличие токена и осуществлять попытку его обновления.
export async function middleware(req) {
const authToken = req.cookies.get('authToken')?.value;
const refreshToken = req.cookies.get('refreshToken')?.value;
if (authToken) {
// Проверка на действительность токена
try {
await jwtVerify(authToken, secret);
return NextResponse.next();
} catch (error) {
// Если токен истек, пытаемся его обновить
if (error.code === 'ERR_JWT_EXPIRED' && refreshToken) {
const newAuthToken = await refreshAuthToken(refreshToken);
if (newAuthToken) {
// Установка нового токена
const response = NextResponse.next();
response.cookies.set('authToken', newAuthToken, { httpOnly: true, path: "/" }); // Используйте корень, если не нужно использовать конкретные домены
return response;
}
}
}
}
// Перенаправление на страницу логина, если токены не действительны
return NextResponse.redirect(new URL("/login", req.url));
}
Обновление серверной логики
В вашем коде для обновления токена, убедитесь, что вы правильно проверяете токены и формируете ответ:
export async function POST() {
try {
const refreshToken = cookies().get('refreshToken')?.value || headers().get('Authorization')?.split(' ')[1];
if (!refreshToken) {
return new Response("Unauthorized", { status: 401 });
}
// Проверьте refreshToken
const { payload } = await jwtVerify(refreshToken, secret);
const userId = payload.userId;
// Работа с БД для получения пользователя
await connectToDB();
const user = await User.findById(userId);
if (!user) {
return new Response("User not found", { status: 404 });
}
// Создайте новый токен аутентификации
const newAuthToken = await new SignJWT({ id: user._id.toString(), ... })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('1h')
.sign(secret);
// Вернем новый токен в куки
const response = NextResponse.json({ newAuthToken });
response.cookies.set('authToken', newAuthToken, { httpOnly: true, path: "/" }); // Укажите правильный путь для куки
return response;
} catch (error) {
console.error("Error refreshing token:", error);
return new Response("Server error", { status: 500 });
}
}
Заключение
Применение JWT для управления сессиями и аутентификацией требует тщательной настройки как на стороне клиента, так и на сервере. Ошибки могут проявляться как на уровне API, так и в коде промежуточного программного обеспечения. Рекомендуется тщательно следить за логи каждого вызова, а также пояснениями, что помогает быстрее находить и устранять ошибки. Используйте опыт из этой ситуации, чтобы укрепить ваши знания в области работы с JWT и интеграции API в Next.js. С правильным подходом, вы сможете создать надежную систему аутентификации для вашего приложения.