Пошук уроків, статей та іншого контенту
Розберете різницю між автентифікацією та авторизацією й визначите їхню роль у захисті NestJS-застосунку.
У захисті застосунку є два основні запитання:
Автентифікація: хто робить запит?
Авторизація: що цій особі дозволено робити?
Ці поняття пов’язані, але не є взаємозамінними.
Автентифікація — це перевірка особи користувача.
Застосунок може перевіряти:
логін і пароль;
токен доступу;
сесію;
API-ключ;
інший спосіб підтвердження особи.
Результатом успішної автентифікації зазвичай стає інформація про користувача, наприклад:
{
id: 42,
email: 'anna@example.com',
role: 'admin'
}У NestJS цю інформацію часто додають до об’єкта запиту:
request.user = user;Після цього інші частини застосунку можуть дізнатися, хто виконує запит.
Авторизація — це перевірка дозволів уже відомого користувача.
Наприклад:
звичайний користувач може переглядати власний профіль;
адміністратор може видаляти користувачів;
редактор може змінювати статті;
гість може переглядати лише відкриті сторінки.
Авторизація відповідає на питання:
Чи має цей користувач право виконати конкретну дію?
Уявімо офісну будівлю:
Працівник показує перепустку на вході.
Система визначає, хто це.
Біля дверей серверної система перевіряє, чи має цей працівник доступ до приміщення.
Перший крок — автентифікація.
Другий крок — авторизація.
Користувач може бути успішно автентифікований, але все одно не мати дозволу на певну дію.
У NestJS для перевірок доступу часто використовують guards.
Guard — це клас, який вирішує, чи може запит продовжити виконання.
Метод canActivate() повертає:
true, якщо запит дозволено;
false, якщо запит потрібно заблокувати.
Зазвичай застосунок використовує два окремі guards:
guard автентифікації визначає користувача;
guard авторизації перевіряє його роль або дозволи.
Послідовність може виглядати так:
HTTP-запит
↓
AuthenticationGuard
↓
request.user
↓
AuthorizationGuard
↓
контролерЯкщо автентифікація не пройдена, до авторизації справа не доходить.
Нижче наведено спрощений, але runnable-приклад NestJS-застосунку.
У ньому:
AuthenticationGuard визначає користувача;
RolesGuard перевіряє його роль;
декоратор @Roles() описує дозволені ролі;
endpoint /admin доступний лише адміністратору.
У прикладі автентифікація імітується заголовком
x-user-roleлише для демонстрації. У реальному застосунку не можна довіряти ролі, переданій клієнтом у звичайному заголовку.
import {
CanActivate,
Controller,
ExecutionContext,
ForbiddenException,
Get,
Injectable,
SetMetadata,
UnauthorizedException,
UseGuards,
} from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Reflector } from '@nestjs/core';
import { Module } from '@nestjs/common';
type Role = 'user' | 'admin';
interface AuthenticatedUser {
id: number;
role: Role;
}
const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
@Injectable()
class AuthenticationGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const userRole = request.headers['x-user-role'];
if (userRole !== 'user' && userRole !== 'admin') {
throw new UnauthorizedException('Користувача не автентифіковано');
}
// У реальному застосунку користувач створюється після перевірки токена або сесії.
request.user = {
id: 1,
role: userRole,
} satisfies AuthenticatedUser;
return true;
}
}
@Injectable()
class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user as AuthenticatedUser | undefined;
if (!user || !requiredRoles.includes(user.role)) {
throw new ForbiddenException('Недостатньо прав');
}
return true;
}
}
@Controller()
class AppController {
@Get('profile')
@UseGuards(AuthenticationGuard)
getProfile() {
return {
message: 'Профіль доступний автентифікованому користувачу',
};
}
@Get('admin')
@UseGuards(AuthenticationGuard, RolesGuard)
@Roles('admin')
getAdminData() {
return {
message: 'Дані доступні лише адміністратору',
};
}
}
@Module({
controllers: [AppController],
providers: [AuthenticationGuard, RolesGuard, Reflector],
})
class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log('Застосунок запущено на http://localhost:3000');
}
bootstrap();Запит до профілю з роллю користувача:
curl -H "x-user-role: user" http://localhost:3000/profileAuthenticationGuard перевіряє заголовок і додає користувача до request.user. Після цього endpoint повертає відповідь.
Запит до адміністративного endpoint із роллю користувача:
curl -H "x-user-role: user" http://localhost:3000/adminАвтентифікація проходить успішно, але RolesGuard відхиляє запит, оскільки роль user не входить до списку дозволених ролей.
Результатом буде помилка 403 Forbidden.
Запит із роллю адміністратора:
curl -H "x-user-role: admin" http://localhost:3000/adminУ цьому випадку:
користувач проходить автентифікацію;
RolesGuard бачить роль admin;
запит передається до методу контролера.
Під час роботи із захистом найчастіше використовують два HTTP-статуси.
401 UnauthorizedОзначає, що користувача не вдалося автентифікувати.
Причини:
відсутні дані для входу;
токен недійсний;
сесія завершилася;
облікові дані неправильні.
Це відповідь на запитання:
Хто ви?
403 ForbiddenОзначає, що користувача розпізнано, але йому заборонено виконувати дію.
Причини:
недостатня роль;
відсутній потрібний дозвіл;
ресурс доступний лише певній групі користувачів.
Це відповідь на запитання:
Ви відомі системі, але вам це не дозволено.
У практичному NestJS-застосунку логіка може бути організована так:
Він:
отримує облікові дані із запиту;
перевіряє їх;
знаходить користувача;
додає користувача до request.user;
відхиляє неавтентифікований запит.
Він:
читає вимоги endpoint;
отримує користувача з request.user;
перевіряє роль або дозвіл;
дозволяє або забороняє дію.
Контролер при цьому зосереджується на бізнес-логіці, а не на повторній перевірці кожного запиту.
У прикладі доступ описується безпосередньо біля endpoint:
@Roles('admin')
@Get('admin')
getAdminData() {
return {
message: 'Доступ дозволено',
};
}Такий підхід називають декларативним: контролер описує, яка роль потрібна, а guard вирішує, як перевірити цю вимогу.
Для кількох ролей можна вказати їх разом:
@Roles('admin', 'user')У наведеній реалізації доступ буде дозволено, якщо роль користувача збігається хоча б з однією роллю зі списку.
Під час проєктування захисту NestJS-застосунку варто пам’ятати:
автентифікація встановлює особу користувача;
авторизація перевіряє його права;
автентифікація має відбуватися до авторизації;
дані про користувача зазвичай зберігаються в request.user;
401 означає проблему з автентифікацією;
403 означає недостатні права;
guard не повинен автоматично вважати будь-які дані від клієнта достовірними;
роль користувача потрібно отримувати з перевіреного джерела.
Перевірка токена показує, хто користувач, але не доводить, що йому дозволена конкретна дія.
Потрібно виконувати обидві перевірки:
користувач автентифікований
+
користувач має потрібний дозвілНаявність токена ще не означає, що він:
дійсний;
не прострочений;
належить потрібному користувачу;
містить достатні права.
Автентифікаційний guard має перевіряти токен, а не лише його присутність.
Клієнт може змінити заголовок, поле форми або параметр URL. Тому роль не можна безпосередньо приймати від клієнта як достовірну інформацію.
У навчальному прикладі заголовок використовується лише для імітації. У реальному застосунку роль має надходити з перевіреного токена або бази даних після успішної автентифікації.
Якщо кожен метод контролера самостійно перевіряє роль, код швидко стає дубльованим і складним для підтримки.
Для таких перевірок краще використовувати guards і метадані, наприклад @Roles().
Автентифікація відповідає на питання: «Хто це?»
Авторизація відповідає на питання: «Що йому дозволено?»
У NestJS для таких перевірок часто використовують guards.
Authentication guard перевіряє особу та додає користувача до request.user.
Authorization guard перевіряє роль або дозволи користувача.
Помилка 401 означає, що користувача не автентифіковано.
Помилка 403 означає, що користувач автентифікований, але не має потрібних прав.
Надійний захист потребує обох етапів і не повинен довіряти неперевіреним даним із запиту.