401 vs 403: в чём разница и когда какой код возвращать
401 Unauthorized vs 403 Forbidden: чем отличаются и как выбрать правильный код
Ошибку 401 Unauthorized и 403 Forbidden часто путают, потому что обе относятся к отказу в доступе. Пользователь видит «не пускает», разработчик получает ответ из семейства 4xx, а в логах остаётся короткая строка со статусом. Но смысл у кодов разный: 401 говорит «сначала представьтесь», а 403 — «я понял, кто вы, но доступ всё равно запрещён».
Путаницу усиливает название Unauthorized: по-английски оно похоже на «не авторизован», хотя в HTTP-семантике 401 в первую очередь про аутентификацию — подтверждение личности: логин, пароль, токен, сессию, сертификат. Авторизация — проверка прав — чаще приводит к 403.
Разберёмся, как выбирать правильный код в API, админках, Nginx и микросервисах, что писать в ответе и как не сломать мониторинг доступности из-за закрытых эндпоинтов.
Коротко: главная разница между 401 и 403
401 Unauthorized возвращают, когда запрос не прошёл аутентификацию: нет токена, токен просрочен, подпись JWT не сходится, cookie сессии отсутствует, пароль неверный.
403 Forbidden возвращают, когда сервер отказывает в доступе, хотя запрос понятен и пользователь может быть известен. Например, пользователь залогинен, но не администратор; токен валиден, но без нужного scope; IP попал в deny-лист; ресурс существует, но закрыт политикой доступа.
Упрощённая формула:
| Ситуация | Код |
|---|---|
| Клиент не прислал credentials | 401 |
| Credentials присланы, но неверные | 401 |
| Токен истёк или невалиден | 401 |
| Пользователь известен, но прав не хватает | 403 |
| Доступ закрыт всем, кроме allow-list | 403 |
| Сервер не хочет раскрывать существование ресурса | часто 404 вместо 403 |
Для 401 есть дополнительное правило: ответ должен содержать заголовок WWW-Authenticate, который объясняет клиенту, как аутентифицироваться. На практике его часто забывают в JSON API, но для корректной HTTP-семантики он нужен.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
Content-Type: application/json
{"error":"invalid_token","message":"Access token is missing or invalid"}Для 403 этот заголовок обычно не нужен:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{"error":"forbidden","message":"You do not have permission to access this resource"}Если нужен общий справочник по соседним статусам, полезно держать под рукой материал про все коды ответов HTTP.
Что означает ошибка 401 Unauthorized
401 означает, что сервер требует аутентификацию, но запрос её не прошёл. Клиент может повторить запрос с корректными данными: заголовком Authorization, cookie сессии, клиентским сертификатом или другим поддерживаемым механизмом.
Типичные причины 401:
1) Нет заголовка Authorization — клиент обращается к защищённому API без токена:
GET /api/me HTTP/1.1
Host: example.comСервер отвечает 401, потому что не знает, кто делает запрос.
2) Неверный формат credentials — например, API ждёт Bearer, а клиент отправляет голый токен:
Authorization: eyJhbGciOi...Вместо:
Authorization: Bearer eyJhbGciOi...3) Токен истёк — распространённый случай для JWT и OAuth2. Access token больше не действителен, клиент должен обновить его через refresh token или пройти логин заново.
4) Подпись токена невалидна — JWT изменили, подписали другим ключом, перепутали issuer, audience, алгоритм или окружение. Например, фронтенд ходит на staging API с production-токеном.
5) Сессия не найдена — cookie есть, но сессия удалена из Redis, истекла или не доехала из-за SameSite, Secure, домена или CORS-настроек.
6) Неверный логин или пароль — при Basic Auth или форме логина сервер не подтверждает личность пользователя.
Несмотря на название, 401 не про «у пользователя нет роли admin» — это про то, что сервер не смог аутентифицировать запрос. При проектировании API стоит разделять: authentication → 401, authorization → 403.
Если вы реализуете JWT в Node.js, похожая логика подробно разобрана в статье про аутентификацию по JWT в Node.js + Express. Для OAuth2 полезен отдельный разбор: как работает OAuth2.
Что означает 403 Forbidden
403 Forbidden означает, что сервер понял запрос, но отказывается его выполнять. В отличие от 401, проблема не решается простой отправкой логина или повторной авторизацией под тем же пользователем — нужно менять права, роль, политику доступа, IP, тариф или сам запрос.
Типичные причины 403:
1) Недостаточно прав — пользователь залогинен, но пытается открыть админский раздел:
GET /admin/users HTTP/1.1
Authorization: Bearer valid-user-tokenЕсли токен валиден, но роль user, а нужна admin, корректный ответ — 403.
2) Не хватает OAuth2 scope — токен действителен, но в нём нет нужного разрешения. Например, есть read:profile, но нет write:billing.
3) Доступ запрещён по IP — Nginx, WAF, CDN или приложение применяет allow-list/deny-list. Для клиента это выглядит как 403, даже если он не проходил пользовательскую аутентификацию.
4) Ресурс закрыт политикой — файл, директория, приватный объект в S3-совместимом хранилище, репозиторий, проект, документ или команда.
5) Запрещён метод — иногда для запрета POST, PUT, DELETE ошибочно возвращают 403. Но если метод в принципе не поддерживается ресурсом, лучше использовать 405 Method Not Allowed. Если метод поддерживается, но конкретному пользователю нельзя — 403.
6) CSRF-защита отклонила запрос — пользователь аутентифицирован cookie, но не прислал корректный CSRF-токен. Многие фреймворки возвращают 403, потому что действие запрещено политикой безопасности.
7) WAF посчитал запрос опасным — SQL-инъекция в параметре, подозрительный user-agent, странный payload. Это тоже часто 403, хотя с точки зрения клиента причина может быть неочевидна.
Для подробного разбора именно этого статуса есть отдельная статья: ошибка 403 Forbidden.
Когда какой код возвращать в API
Правильный выбор между 401 и 403 влияет на поведение клиента: показывать форму логина, обновлять токен, просить права, скрывать кнопку или открывать экран «нет доступа».
1) Нет credentials — 401 — если эндпоинт требует аутентификацию, а запрос пришёл без Authorization, session cookie, API key или mTLS-сертификата.
2) Credentials есть, но невалидны — 401 — неверный API key, просроченный JWT, битая подпись, неизвестная сессия, неправильный пароль.
3) Credentials валидны, но прав не хватает — 403 — пользователь определён, но не может выполнить действие: удалить чужой проект или вызвать endpoint только для billing-admin.
4) Нужна другая учётная запись — 403, не 401 — если повторный логин тем же пользователем не поможет, это не 401. Пользователю нужны другие права, членство в организации, тариф или роль.
5) Можно раскрыть существование ресурса — 403 — если безопасно сообщить «ресурс есть, но доступ запрещён».
6) Нельзя раскрывать существование ресурса — 404 — если по URL можно подбирать приватные документы, проекты или пользователей, иногда лучше вернуть 404 Not Found, даже если объект существует, чтобы не давать атакующему оракул существования ресурсов.
Пример с проектами:
| Запрос | Условие | Ответ |
|---|---|---|
GET /projects/123 | нет токена | 401 |
GET /projects/123 | токен просрочен | 401 |
GET /projects/123 | пользователь есть, но не участник проекта | 403 или 404 |
DELETE /projects/123 | участник есть, но нет роли owner | 403 |
GET /projects/999 | проекта нет | 404 |
Отделяйте эти случаи от 400 Bad Request, 404 Not Found, 409 Conflict и 422 Unprocessable Content: 401 и 403 не должны становиться универсальным ответом на любой «плохой» запрос.
Если вы проектируете API с нуля, посмотрите практические материалы про FastAPI или REST API на Node.js без Express: там проще увидеть, где размещать middleware аутентификации и проверки прав.
Заголовок WWW-Authenticate и тело ответа
У 401 есть особенность: сервер должен вернуть WWW-Authenticate. Заголовок сообщает клиенту, какая схема аутентификации нужна. Без него браузеры, HTTP-клиенты и библиотеки могут хуже понимать, что делать дальше.
Для Basic:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Admin area"Для Bearer-токенов:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token", error_description="The access token expired"В JSON API тело ответа обычно делают машинно-читаемым:
{
"error": "invalid_token",
"message": "Access token expired"
}Здесь есть баланс между удобством и безопасностью.
1) Не раскрывайте лишнее при логине — для пары логин/пароль лучше не писать «пользователь найден, пароль неверный». Безопаснее: «неверный логин или пароль». Иначе вы помогаете перебирать аккаунты.
2) Различайте ошибки для доверенных клиентов — мобильному приложению или SPA полезно знать token_expired, invalid_token, session_revoked, чтобы корректно обновить токен или отправить пользователя на логин.
3) Не путайте error и HTTP-статус — при статусе 401 поле error может быть invalid_token, missing_token, expired_token. При 403 — insufficient_scope, role_required, plan_restricted.
4) Для OAuth2 используйте стандартные подсказки — invalid_token обычно ведёт к 401, insufficient_scope — к 403. При insufficient_scope можно вернуть WWW-Authenticate с описанием нужного scope, но сам статус остаётся 403.
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "insufficient_scope",
"required_scope": "billing:write"
}Чего лучше избегать: возвращать 200 OK с телом {"error":"forbidden"}. Это ломает клиентов, кэши, мониторинг HTTP API и любые инструменты, ориентирующиеся на статус-коды.
Частые ошибки в реализации
Даже опытные команды иногда выбирают коды не по смыслу, а «как исторически сложилось». Потом это всплывает в SDK, фронтенде, алертах и интеграциях.
1) Всегда возвращать 403 для закрытых endpoint’ов — пользователь без токена получает 403, фронтенд показывает «нет прав», хотя надо показать форму входа. Правильнее: нет токена → 401, токен валиден, но прав нет → 403.
2) Всегда возвращать 401 для любых проблем с доступом — админская кнопка вызывает API, пользователь залогинен, но получает 401. SPA решает, что сессия умерла, делает logout, хотя проблема была в роли.
3) Использовать 401 для заблокированного аккаунта — если пользователь успешно аутентифицирован, но аккаунт заблокирован, чаще подходит 403.
4) Возвращать 403 вместо 429 при rate limiting — если клиент превысил лимит запросов, правильный статус — 429 Too Many Requests. 403 подходит, когда доступ запрещён политикой, а не временным лимитом.
5) Возвращать 403 вместо 451 — если ресурс недоступен по юридическим причинам, есть специальный код 451 Unavailable For Legal Reasons, семантически он точнее.
6) Прятать все приватные ресурсы за 404 — это допустимо для безопасности, но не стоит делать слепо: внутренним пользователям и поддержке полезно отличать «не найдено» от «нет доступа». Иногда лучше возвращать 403 в админке и 404 в публичном API.
7) Не логировать причину отказа — клиенту не всегда нужно раскрывать детали, но серверные логи должны содержать причину: missing_token, expired_token, invalid_signature, insufficient_role, ip_denied, csrf_failed. Иначе расследование превращается в угадывание.
8) Кэшировать 401 и 403 без контроля — ответы доступа зависят от пользователя и токена. Следите за Cache-Control, Vary: Authorization, поведением CDN и reverse proxy, иначе один пользователь может увидеть отказ, предназначенный другому.
401 и 403 в Nginx, прокси и инфраструктуре
Не все 401 и 403 генерирует приложение. Иногда код приходит от Nginx, CDN, WAF, API Gateway, ingress-контроллера или балансировщика. Это особенно заметно в Kubernetes и микросервисах: приложение «ничего не видело», а клиент уже получил отказ.
Типичные инфраструктурные источники:
1) auth_basic в Nginx — если включена базовая аутентификация, Nginx сам вернёт 401 и заголовок WWW-Authenticate.
location /admin/ {
auth_basic "Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
}Если пользователь не ввёл логин/пароль или ввёл неверные данные, приложение даже не получит запрос.
2) allow/deny в Nginx — запрет по IP обычно приводит к 403.
location /internal/ {
allow 10.0.0.0/8;
deny all;
}Для клиента это «доступ запрещён», хотя никакой пользовательской аутентификации не было.
3) Ошибки прав на файлы — веб-сервер может вернуть 403, если не может прочитать файл или директорию, нет index-файла, запрещён directory listing. Классический случай для статических сайтов.
4) WAF и CDN — Cloudflare, ModSecurity и другие фильтры могут отвечать 403, если запрос похож на атаку. В логах приложения такого запроса не будет, искать нужно на уровне edge/proxy.
5) External auth в ingress/API Gateway — gateway может проверять JWT, API key или OAuth2-токен до приложения. Невалидный токен даст 401, нехватка scope — 403, если правила настроены корректно.
6) mTLS — если клиентский сертификат обязателен, сбой может произойти ещё на TLS-уровне, до HTTP-статуса. Но если сертификат проверяет приложение или прокси после рукопожатия, возможны 401 или 403 в зависимости от логики.
Чтобы понять, кто именно вернул код, смотрите заголовки (Server, Via, X-Request-Id), access logs на каждом уровне и трассировку запроса. Для ручной проверки удобно использовать curl; если нужно освежить базовые приёмы, есть отдельный гайд: что такое curl и как им пользоваться.
Как диагностировать ошибку 401 или 403
Диагностику лучше вести от клиента к серверу: что отправили, где запрос прошёл, кто вернул статус, какая внутренняя причина записана в лог.
1) Проверьте фактический запрос — не верьте только коду фронтенда. Откройте DevTools или повторите запрос через curl:
curl -i https://api.example.com/me
curl -i -H "Authorization: Bearer $TOKEN" https://api.example.com/meСравните заголовки, cookie, метод, URL, Origin, Content-Type.
2) Посмотрите WWW-Authenticate — для 401 он часто сразу объясняет причину: invalid_token, expired, realm, нужная схема.
3) Декодируйте токен без доверия к содержимому — JWT можно декодировать локально и проверить exp, iss, aud, sub, scope, roles. Но подпись должна проверяться сервером, а не «на глаз».
4) Проверьте время — рассинхронизация часов ломает exp, nbf, одноразовые подписи, OAuth2 flow и signed URLs. На серверах должен работать NTP/chrony.
5) Сверьте окружения — production API, staging auth-сервер, старый public key, другой client_id, неправильный redirect URI — частые причины 401.
6) Найдите уровень, который ответил — если в логах приложения пусто, смотрите Nginx, ingress, CDN, WAF, API Gateway. 403 от Nginx из-за deny all и 403 от приложения из-за роли — разные задачи.
7) Проверьте CORS и cookie — браузер может не отправлять cookie из-за SameSite, отсутствия credentials: "include", неправильного домена или Secure. В итоге сервер видит анонимный запрос и возвращает 401, хотя в Postman всё работает.
8) Проверьте CSRF — если cookie есть, пользователь залогинен, но POST/PUT возвращает 403, ищите CSRF-токен, заголовок, origin/referrer-проверку.
9) Проверьте права в данных — роль в токене может быть старой, а актуальные права лежат в базе. Или наоборот: токен содержит admin, но приложение проверяет membership в организации.
10) Сопоставьте с деплоем — массовые 401 после релиза часто связаны с ротацией ключей, сменой cookie-domain, изменением JWT audience или настройками gateway.
Для продакшена полезно отслеживать долю 4xx отдельно от 5xx. 401 и 403 не всегда означают недоступность сайта — это может быть нормальный отказ для закрытого раздела. Но резкий всплеск 401 на публичном API после деплоя — уже сигнал. В синтетическом мониторинге endpoint’ов задавайте ожидаемый статус: для публичной страницы это обычно 200, для закрытого URL может быть ожидаемый 401. Statuser проверяет сайт с заданным интервалом и присылает уведомление о сбое; важно настроить проверку так, чтобы «штатный отказ» не считался аварией.
Если мониторите API глубже, пригодится материал про мониторинг HTTP API: RED-метрики, latency и error budget.
Практические правила для разработки
Набор правил, который помогает держать доступы предсказуемыми для backend, frontend, mobile, DevOps и поддержки.
1) Разделите middleware аутентификации и авторизации — первый слой устанавливает user или возвращает 401. Второй проверяет права и возвращает 403. Не смешивайте это в одном большом if.
2) Используйте единый формат ошибок, например:
{
"error": "forbidden",
"message": "You do not have permission to perform this action",
"request_id": "req_..."
}Клиентам проще обрабатывать ответы, а поддержке — искать событие по request_id.
3) Документируйте статусы в OpenAPI — для каждого защищённого endpoint’а укажите 401 и 403 отдельно. Это дисциплинирует реализацию и помогает SDK.
4) Не показывайте пользователю технические причины — «JWT signature verification failed» не должен уходить в UI. Пользовательский текст: «Сессия истекла, войдите снова». Техническая причина — в лог.
5) Логируйте безопасно — не пишите полный токен, пароль, cookie или API key. Достаточно хэша, первых символов идентификатора, sub, client_id, причины отказа и request_id.
6) Продумайте поведение фронтенда — при 401 обычно нужен refresh token flow или переход на логин. При 403 — экран «нет доступа», скрытие действия или предложение запросить права.
7) Не используйте 403 для бизнес-ошибок без доступа — если пользователь не может оплатить заказ из-за статуса заказа, это может быть 409 Conflict или 422, а не обязательно 403. 403 — про запрет политикой доступа.
8) Учитывайте service-to-service запросы — в микросервисах 401 означает, что сервис не предоставил валидную identity: mTLS, JWT, SPIFFE ID, API key. 403 — identity валидна, но политика не разрешает действие.
9) Тестируйте матрицу доступов — автотесты должны проверять не только «админ может», но и «аноним получает 401», «обычный пользователь получает 403», «чужой ресурс скрывается за 404».
10) Настройте мониторинг по смыслу — не все 4xx требуют алерта, но если закрытый endpoint внезапно стал отдавать 200 без токена, это критичнее, чем всплеск обычных 401. Для внешних проверок доступности задавайте URL, метод, заголовки и ожидаемый код осознанно. Health check лучше делать отдельным безопасным endpoint’ом, а не проверять приватную админку.
FAQ
Почему ошибка 401 называется Unauthorized, если речь про аутентификацию?
Так сложилось исторически в HTTP. На практике 401 означает, что запрос не прошёл проверку credentials, то есть клиенту нужно аутентифицироваться или прислать корректные данные.
Можно ли вернуть 403, если пользователь не залогинен?
Технически можно, но обычно это плохая идея. Если ресурс требует логин, а credentials нет, правильнее вернуть 401. 403 стоит использовать, когда пользователь известен или доступ запрещён политикой независимо от логина.
Что возвращать при истёкшем JWT: 401 или 403?
Обычно 401. Истёкший JWT больше не является валидными credentials. Клиент должен обновить access token или отправить пользователя на повторный вход.
Что лучше: 403 или 404 для чужого приватного ресурса?
Если можно раскрывать факт существования ресурса — 403. Если нельзя показывать, что объект существует, возвращайте 404. Главное — закрепить это правило в API и тестах.
Похожие статьи

Ошибка 403 Forbidden: что означает и как исправить
Практическое руководство по диагностике и исправлению HTTP 403 на сайте, в API, Nginx, Apache, CDN и WAF.
17 августа 202610 мин

Firewall stateful vs stateless: в чём разница и что выбрать
Подробное сравнение stateful и stateless firewall: таблица состояний, conntrack, производительность, безопасность и реальные сценарии применения.
2 марта 20267 мин

Как работает Linux I/O scheduler и когда его стоит менять
Практическое объяснение работы I/O scheduler в Linux: принципы планирования дисковых операций, обзор популярных алгоритмов и рекомендации по выбору.
26 января 20266 мин
Настроить мониторинг за 30 секунд
Надежные оповещения о даунтаймах. Без ложных срабатываний