Аутентификация в API
Токен в заголовке
export async function GET(request: Request) {
const auth = request.headers.get('authorization');
if (!auth?.startsWith('Bearer ')) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = auth.slice(7);
const user = await verifyToken(token);
if (!user) {
return Response.json({ error: 'Invalid token' }, { status: 401 });
}
return Response.json({ user });
}
Cookies
import { cookies } from 'next/headers';
export async function GET() {
const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
if (!token) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const user = await verifyToken(token);
if (!user) {
return Response.json({ error: 'Invalid token' }, { status: 401 });
}
return Response.json({ user });
}
JWT
npm install jose
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export async function createToken(payload: { userId: number }) {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
}
export async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, secret);
return payload;
} catch {
return null;
}
}
jose работает в Edge Runtime, в отличие от jsonwebtoken.
Вспомогательная функция
// lib/auth.ts
import { cookies } from 'next/headers';
export async function getCurrentUser() {
const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
if (!token) return null;
const payload = await verifyToken(token);
if (!payload) return null;
return db.user.findUnique({ where: { id: payload.userId } });
}
Использование
export async function GET() {
const user = await getCurrentUser();
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const posts = await db.post.findMany({ where: { authorId: user.id } });
return Response.json(posts);
}
Проверка ролей
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const user = await getCurrentUser();
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
const { id } = await params;
const post = await db.post.findUnique({ where: { id: Number(id) } });
if (!post) return Response.json({ error: 'Not found' }, { status: 404 });
if (post.authorId !== user.id && user.role !== 'admin') {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
await db.post.delete({ where: { id: Number(id) } });
return new Response(null, { status: 204 });
}
API-ключи
export async function GET(request: Request) {
const apiKey = request.headers.get('x-api-key');
const client = await db.client.findUnique({ where: { apiKey } });
if (!client) {
return Response.json({ error: 'Invalid API key' }, { status: 401 });
}
return Response.json({ client });
}
Rate limiting по ключу
const limits = new Map<string, number[]>();
export async function GET(request: Request) {
const apiKey = request.headers.get('x-api-key') ?? 'anonymous';
const now = Date.now();
const window = 60_000;
const limit = 100;
const timestamps = (limits.get(apiKey) ?? []).filter((t) => now - t < window);
if (timestamps.length >= limit) {
return Response.json(
{ error: 'Rate limit exceeded' },
{
status: 429,
headers: { 'Retry-After': '60' },
}
);
}
timestamps.push(now);
limits.set(apiKey, timestamps);
// обработка
}
Установка cookie
export async function POST(request: Request) {
const { email, password } = await request.json();
const user = await db.user.findUnique({ where: { email } });
if (!user || !(await verifyPassword(password, user.password))) {
return Response.json({ error: 'Invalid credentials' }, { status: 401 });
}
const token = await createToken({ userId: user.id });
const response = Response.json({ user: { id: user.id, email: user.email } });
response.headers.set(
'Set-Cookie',
`token=${token}; HttpOnly; Path=/; Max-Age=604800; SameSite=Lax${
process.env.NODE_ENV === 'production' ? '; Secure' : ''
}`
);
return response;
}
Выход
export async function POST() {
const response = Response.json({ ok: true });
response.headers.set('Set-Cookie', 'token=; HttpOnly; Path=/; Max-Age=0');
return response;
}
Итоги
- Токен в заголовке или куке
joseдля JWT в Edge- Вспомогательная функция для пользователя
- Проверка прав на каждой операции
- API-ключи в заголовке
- Rate limiting по ключу