Пошук уроків, статей та іншого контенту
Розберемо різницю між перевіркою особи користувача та визначенням дозволених йому дій у Node.js API.
Під час роботи з API потрібно відповісти на два різні запитання:
Хто виконує запит?
Що цій особі дозволено робити?
Ці перевірки називаються відповідно:
автентифікація — перевірка особи користувача;
авторизація — перевірка дозволів користувача.
Попри схожі назви, це різні етапи.
Автентифікація підтверджує, що користувач є саме тією особою, за яку себе видає.
Наприклад, користувач може надати:
логін і пароль;
сесійну cookie;
токен доступу;
API-ключ.
Після успішної автентифікації сервер отримує ідентифікатор користувача. Наприклад:
Користувач із id 42 виконує запитСам факт того, що користувач увійшов у систему, ще не означає, що він може виконувати будь-які дії.
Авторизація визначає, які дії дозволені вже ідентифікованому користувачу.
Наприклад:
звичайний користувач може переглядати власний профіль;
адміністратор може переглядати список усіх користувачів;
редактор може змінювати статті;
користувач не може видаляти чужі дані.
Авторизація використовує інформацію про користувача, отриману під час автентифікації: його роль, права, власника ресурсу та інші правила доступу.
Уявімо офісну будівлю:
На вході ви показуєте документ — це автентифікація.
Охорона перевіряє, до яких поверхів ви маєте доступ — це авторизація.
Якщо особу не вдалося підтвердити, доступ до будівлі не надається. Якщо особу підтверджено, але доступу до певного поверху немає, у доступі до цього поверху буде відмовлено.
Для цих ситуацій API зазвичай використовує різні статус-коди.
401 UnauthorizedСтатус 401 означає, що автентифікація не пройдена.
Причини:
заголовок із токеном відсутній;
токен неправильний;
токен прострочений.
Сервер не може встановити, хто виконує запит.
403 ForbiddenСтатус 403 означає, що користувач відомий, але не має потрібних дозволів.
Наприклад, звичайний користувач намагається відкрити адміністративний маршрут.
Різниця коротко:
401 — "Я не знаю, хто ви"
403 — "Я знаю, хто ви, але вам це заборонено"Зазвичай запит обробляється в такому порядку:
Сервер отримує облікові дані або токен.
Автентифікація перевіряє ці дані.
Сервер визначає користувача.
Авторизація перевіряє його роль або права.
Якщо всі перевірки успішні, виконується основна логіка маршруту.
Авторизацію не можна виконати надійно без автентифікації. Спочатку потрібно знати, хто саме виконує запит.
Нижче наведено невеликий сервер без додаткових бібліотек. Він демонструє:
автентифікацію за Bearer-токеном;
отримання профілю авторизованого користувача;
обмеження адміністративного маршруту;
різницю між відповідями 401 і 403.
Токени в прикладі зберігаються в пам’яті лише для навчання. У реальному застосунку не слід жорстко кодувати такі токени в програмі.
const http = require('node:http');
const { URL } = require('node:url');
const usersByToken = new Map([
['user-token', { id: 1, name: 'Олена', role: 'user' }],
['admin-token', { id: 2, name: 'Андрій', role: 'admin' }],
]);
function sendJson(response, statusCode, data) {
response.writeHead(statusCode, {
'Content-Type': 'application/json; charset=utf-8',
});
response.end(JSON.stringify(data));
}
function authenticate(request) {
const header = request.headers.authorization;
if (!header || !header.startsWith('Bearer ')) {
return null;
}
const token = header.slice('Bearer '.length);
return usersByToken.get(token) || null;
}
function authorizeAdmin(user) {
return user.role === 'admin';
}
const server = http.createServer((request, response) => {
const requestUrl = new URL(
request.url,
`http://${request.headers.host || 'localhost'}`
);
if (request.method !== 'GET') {
sendJson(response, 405, {
error: 'Метод не підтримується',
});
return;
}
if (requestUrl.pathname === '/profile') {
const user = authenticate(request);
if (!user) {
sendJson(response, 401, {
error: 'Потрібна автентифікація',
});
return;
}
sendJson(response, 200, {
message: 'Профіль користувача',
user,
});
return;
}
if (requestUrl.pathname === '/admin') {
const user = authenticate(request);
if (!user) {
sendJson(response, 401, {
error: 'Потрібна автентифікація',
});
return;
}
if (!authorizeAdmin(user)) {
sendJson(response, 403, {
error: 'Недостатньо прав',
});
return;
}
sendJson(response, 200, {
message: 'Адміністративні дані',
user,
});
return;
}
sendJson(response, 404, {
error: 'Маршрут не знайдено',
});
});
server.listen(3000, () => {
console.log('Сервер запущено на http://localhost:3000');
});Збережіть код у файл server.js і запустіть:
node server.jscurl http://localhost:3000/profileВідповідь матиме статус 401, тому що сервер не знає, хто виконує запит.
curl -H "Authorization: Bearer user-token" \
http://localhost:3000/profileТакий запит успішний: токен пройшов автентифікацію.
Той самий користувач не може отримати адміністративні дані:
curl -i -H "Authorization: Bearer user-token" \
http://localhost:3000/adminСервер поверне 403, оскільки користувач відомий, але його роль — user.
curl -H "Authorization: Bearer admin-token" \
http://localhost:3000/adminЦей запит успішний, бо:
токен належить відомому користувачу;
користувач має роль admin.
Функція authenticate виконує автентифікацію:
function authenticate(request) {
const header = request.headers.authorization;
if (!header || !header.startsWith('Bearer ')) {
return null;
}
const token = header.slice('Bearer '.length);
return usersByToken.get(token) || null;
}Вона:
читає заголовок Authorization;
перевіряє формат Bearer token;
шукає токен;
повертає користувача або null.
Функція authorizeAdmin виконує авторизацію:
function authorizeAdmin(user) {
return user.role === 'admin';
}Вона не перевіряє токен і не встановлює особу користувача. Вона лише перевіряє, чи має вже знайдений користувач необхідну роль.
Один із поширених способів реалізації авторизації — ролі.
Наприклад:
user — може переглядати власні дані
editor — може створювати та редагувати статті
admin — має доступ до адміністративних операційПеревірка ролі може виглядати так:
if (user.role !== 'admin') {
// Користувач автентифікований, але не має потрібної ролі
return sendJson(response, 403, {
error: 'Недостатньо прав',
});
}У невеликих системах ролей може бути достатньо. У складніших системах перевіряють конкретні дозволи, наприклад:
articles:read
articles:create
articles:update
articles:deleteВажливо, що перевірка має виконуватися на сервері. Не можна покладатися лише на те, що клієнт приховав кнопку або сторінку.
Розглянемо два запити:
Користувач увійшов у систему.Це означає, що автентифікація успішна.
Користувач може видалити цей запис.Це вже твердження про авторизацію, і його потрібно перевірити окремо.
Навіть якщо користувач має дійсний токен, сервер повинен перевірити:
чи має він потрібну роль;
чи має він конкретний дозвіл;
чи є ресурс його власністю;
чи дозволена операція в поточному стані ресурсу.
401 для недостатніх правЯкщо користувач успішно автентифікований, але не має дозволу, потрібно повертати 403, а не 401.
Немає або неправильний токен → 401
Є правильний токен, але немає дозволу → 403Не можна довіряти ролі, отриманій безпосередньо з тіла запиту або звичайного клієнтського коду:
{
"role": "admin"
}Користувач може змінити такі дані перед відправленням. Роль повинна надходити з перевіреного джерела на сервері.
Приховування кнопки в інтерфейсі не захищає API. Користувач може вручну надіслати HTTP-запит.
Кожен захищений маршрут повинен перевіряти дозволи на сервері.
Спочатку потрібно визначити користувача, а потім перевірити його права:
автентифікація → авторизація → виконання операціїРоль користувача — не єдина умова доступу. Наприклад, користувач може мати право редагувати свій профіль, але не профіль іншої людини.
Тому серверу іноді потрібно перевіряти не лише роль, а й належність ресурсу користувачу.
Для кожного маршруту поставте такі запитання:
Чи потрібна автентифікація?
Як сервер отримує облікові дані?
Що відбувається, якщо облікові дані відсутні або неправильні?
Яка роль або дозвіл потрібні для маршруту?
Що відбувається, якщо користувач автентифікований, але не має доступу?
Чи потрібно перевірити власника конкретного ресурсу?
Автентифікація відповідає на запитання: «Хто це?».
Авторизація відповідає на запитання: «Що йому дозволено?».
Автентифікація виконується перед авторизацією.
Для відсутнього або неправильного облікового запису зазвичай використовують 401.
Для автентифікованого користувача без потрібних прав використовують 403.
Перевірки доступу повинні виконуватися на сервері, а не лише в клієнтському інтерфейсі.
Наявність дійсного токена сама по собі не означає, що користувач може виконувати будь-яку операцію.