Logo David Burdelak
Webdevelopment

Błąd 401 (Unauthorized) - co oznacza i jak go naprawić?

Błąd 401 (Unauthorized)

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:

PrzyczynaTypowa sytuacja
brak nagłówka Authorizationaplikacja nie dołącza tokena albo serwer go usuwa po drodze
wygasły tokentoken JWT lub OAuth ma krótki czas życia, a aplikacja nie odświeża go automatycznie
zły format nagłówkaliterówka w słowie Bearer, brak spacji, token z cudzysłowem albo znakiem nowej linii
nieprawidłowe daneklucz z innego środowiska, np. testowy klucz API na produkcji
ochrona hasłem na serwerzekatalog lub cała strona zabezpieczona przez .htpasswd albo w panelu hostingu
różnica czasuzegar 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: