Błąd 401 Unauthorized oznacza, że serwer wymaga uwierzytelnienia, a żądanie nie zawierało poprawnych danych: hasła, tokena albo klucza API. Mimo nazwy nie chodzi o brak uprawnień, tylko o to, że serwer nie wie, kim jest klient. Spotykam go najczęściej przy integracjach z API, stronach testowych zabezpieczonych hasłem i wtyczkach korzystających z REST API WordPressa. W tym wpisie pokazuję, jak diagnozuję błąd 401 i jakie przyczyny najczęściej za nim stoją.
Dlaczego serwer zwraca błąd 401
Serwer odpowiada kodem 401, gdy zasób wymaga uwierzytelnienia, a klient go nie przeszedł. Poprawna odpowiedź 401 zawiera nagłówek
WWW-Authenticate, który mówi, jakiej metody uwierzytelnienia oczekuje serwer, np. Basic albo Bearer.
W praktyce przyczyny są powtarzalne:
| Przyczyna | Typowa sytuacja |
|---|---|
brak nagłówka Authorization | aplikacja nie dołącza tokena albo serwer go usuwa po drodze |
| wygasły token | token JWT lub OAuth ma krótki czas życia, a aplikacja nie odświeża go automatycznie |
| zły format nagłówka | literówka w słowie Bearer, brak spacji, token z cudzysłowem albo znakiem nowej linii |
| nieprawidłowe dane | klucz z innego środowiska, np. testowy klucz API na produkcji |
| ochrona hasłem na serwerze | katalog lub cała strona zabezpieczona przez .htpasswd albo w panelu hostingu |
| różnica czasu | zegar serwera spieszy się lub spóźnia i świeży token uznawany jest za wygasły albo jeszcze nieważny |
Jak diagnozuję i naprawiam błąd 401
1. Sprawdzam, co faktycznie wysyła klient
W narzędziach deweloperskich przeglądarki, w zakładce Network, otwieram żądanie z błędem i sprawdzam, czy nagłówek Authorization
w ogóle się w nim pojawia i ma poprawny format. To samo żądanie powtarzam poza aplikacją, żeby wykluczyć błąd w jej kodzie:
curl -i https://api.example.pl/v1/orders \
-H "Authorization: Bearer TOKEN"
Jeśli z curl działa, a z aplikacji nie, problem leży w kodzie klienta. Jeśli nie działa w obu przypadkach, sprawdzam token
i serwer. Przy tokenach JWT dekoduję ich zawartość i sprawdzam pole exp, czyli datę wygaśnięcia. To ten sam sposób pracy, którego używam, gdy muszę
debugować kod JavaScript i PHP po stronie klienta i serwera.
2. Sprawdzam, czy serwer nie gubi nagłówka
To przyczyna, którą widzę najczęściej przy stronach na Apache z PHP uruchamianym przez FastCGI. Klient wysyła poprawny nagłówek
Authorization, ale serwer nie przekazuje go do PHP, więc aplikacja widzi żądanie bez danych logowania. Typowy objaw to działające
logowanie w panelu WordPressa i jednoczesny błąd 401 przy wywołaniach REST API z hasłem aplikacji. Rozwiązuje to reguła w
.htaccess:
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
Na Nginx za proxy sprawdzam z kolei, czy nagłówek jest przekazywany dalej do aplikacji i czy CDN lub WAF go nie usuwa.
3. Sprawdzam odświeżanie tokenów i czas serwera
Gdy błąd pojawia się po pewnym czasie od zalogowania, winny jest zwykle brak odświeżania tokena. Aplikacja powinna przy odpowiedzi 401
raz spróbować odświeżyć token i powtórzyć żądanie, a dopiero potem wylogować użytkownika. Jeśli błąd dotyczy świeżo wydanych tokenów,
sprawdzam synchronizację czasu na serwerze poleceniem timedatectl.
4. Sprawdzam ochronę hasłem na serwerze
Strony testowe zabezpieczam hasłem na poziomie serwera i to dobra praktyka, ale po przeniesieniu takiej konfiguracji na produkcję cała
strona zaczyna zwracać 401. Po każdej migracji sprawdzam, czy w .htaccess nie zostały dyrektywy AuthType
i Require valid-user, a w panelu hostingu ochrona katalogów.
Błąd 401 a 403 i bezpieczeństwo logowania
Błąd 401 dotyczy uwierzytelnienia, czyli pytania „kim jesteś”. Błąd 403 dotyczy autoryzacji, czyli pytania „co możesz zrobić”: serwer wie, kim jest klient, ale nie daje mu dostępu. Odpowiedź 401 nie powinna też zdradzać, czy konto istnieje, więc przy logowaniu zwracam ten sam komunikat dla złego loginu i złego hasła. Seria odpowiedzi 401 z jednego adresu w logach to typowy ślad prób zgadywania haseł, przed którymi chroni zabezpieczenie logowania przed atakami brute force. Same hasła po stronie serwera przechowuję wyłącznie jako skróty, tak jak przy każdym projekcie, w którym trzeba bezpiecznie przechowywać hasła w bazie danych.
Strony zwracające 401 nie są indeksowane, bo Googlebot nie loguje się na stronach. To pożądane dla panelu klienta i stron testowych, ale jeśli publiczna podstrona przypadkiem zwraca 401, znika z wyników wyszukiwania.
Pozostałe błędy HTTP
Każdy kod błędu wskazuje na inną warstwę, więc diagnozę zaczynam od innego miejsca: