Código de status HTTP · Erros do cliente (4xx)
Erro 401 Unauthorized
O erro 401 Unauthorized significa que o servidor não aceitou a sua identificação: o pedido chegou sem credenciais, ou com credenciais inválidas ou vencidas. Apesar do nome, é um problema de autenticação, e a causa mais comum é uma sessão ou token de acesso expirado.
| Classe | 4xx, Erros do cliente |
|---|---|
| Definido em | RFC 9110 §15.5.2 |
| Pode ir para o cache por padrão | Só com Cache-Control ou Expires explícitos |
| Pode repetir o pedido | Sim, com credenciais novas ou válidas no cabeçalho Authorization |
| Cabeçalhos relevantes |
|
O que significa o 401
A RFC 9110, seção 15.5.2, define o 401 como um pedido sem credenciais de autenticação válidas para o recurso. O nome "Unauthorized" é um deslize histórico: o código trata de identidade (autenticação), enquanto recusar alguém que o servidor já identificou é o 403.
Quem envia 401 é obrigado a incluir o cabeçalho WWW-Authenticate com pelo menos um desafio, como Basic realm="admin" ou Bearer realm="api". O desafio diz ao cliente como se autenticar. Com Basic, o navegador abre a janelinha nativa de login; com Bearer, servidores OAuth 2.0 acrescentam error="invalid_token" (RFC 6750) para indicar que o token foi recusado, e não que faltou.
Se o pedido já tinha credenciais, o 401 quer dizer que elas foram rejeitadas. O cliente pode tentar de novo com outro cabeçalho Authorization, que é justamente o que a lógica de renovação de token faz: captura o 401, troca o refresh token por um novo access token e repete o pedido uma vez.
Causas comuns
Se você está visitando o site
- A sua sessão expirou ou você saiu da conta em outra aba, e o site não reconhece mais você.
- Usuário ou senha errados numa página protegida por autenticação HTTP Basic (a janelinha do navegador volta sempre).
- Cookies de terceiros bloqueados quando o login fica em outro domínio que não o da página.
Se você administra o servidor
- JWT ou access token OAuth expirado, muitas vezes sem lógica de renovação no cliente.
- Cabeçalho Authorization malformado: sem o prefixo "Bearer ", token com aspas ou quebra de linha, credenciais Basic sem base64.
- Um proxy, CDN ou balanceador removendo o cabeçalho Authorization antes de ele chegar à aplicação.
- Relógios dessincronizados entre servidores, e um token recém-emitido parece ainda não válido (nbf) ou já vencido (exp).
- Preflight de CORS: o navegador envia OPTIONS sem credenciais e um middleware que exige login em todo método responde 401, o que o navegador mostra como erro de CORS.
Como resolver
Se você está visitando o site
- Saia da conta e entre de novo; isso gera uma sessão nova.
- Apague os cookies do site se, logo depois do login, você cair de novo no 401.
- Na janelinha de autenticação Basic, confirme o usuário e a senha com quem administra o site; o navegador guarda os dados até ser fechado.
Se você administra o servidor
- Decodifique o token (um decodificador de JWT mostra exp, nbf, iss e aud) e compare as claims com o que a API espera.
- Implemente renovação ao receber 401: renove o access token uma vez, repita o pedido e deslogue o usuário se a renovação também falhar.
- Deixe os pedidos OPTIONS passarem pelo middleware de autenticação para o preflight de CORS funcionar.
- Verifique se cada proxy no caminho repassa o Authorization; algumas configurações exigem isso de forma explícita.
- Inclua sempre o WWW-Authenticate; clientes e bibliotecas dependem dele, e a especificação exige.
Como enviar um 401
O auth_basic do nginx envia o 401 e o desafio WWW-Authenticate: Basic por você; crie o arquivo de senhas com o htpasswd.
app.get('/me', (req, res) => {
res.set('WWW-Authenticate', 'Bearer realm="api", error="invalid_token"');
res.status(401).json({ error: 'Access token is missing or expired' });
});// app/me/route.ts
export async function GET() {
return Response.json(
{ error: 'Access token is missing or expired' },
{ status: 401, headers: { 'WWW-Authenticate': 'Bearer realm="api", error="invalid_token"' } }
);
}mux.HandleFunc("GET /me", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Bearer realm="api", error="invalid_token"`)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized) // 401
w.Write([]byte(`{"error":"Access token is missing or expired"}`))
})from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/me")
def get_me():
raise HTTPException(
status_code=401,
detail="Access token is missing or expired",
headers={"WWW-Authenticate": 'Bearer realm="api", error="invalid_token"'},
)# nginx answers 401 with a WWW-Authenticate: Basic challenge
location /admin/ {
auth_basic "Admin area";
auth_basic_user_file /etc/nginx/.htpasswd;
}Costuma ser confundido com
- 401 vs 403
- O 401 quer dizer "não sei quem você é, autentique-se"; o 403 quer dizer "sei quem você é, ou tanto faz, e a resposta é não".
- 401 vs 407
- O 407 é o mesmo desafio vindo de um proxy no meio do caminho, com Proxy-Authenticate no lugar de WWW-Authenticate.
Perguntas frequentes
- Qual a diferença entre erro 401 e 403?
- O 401 é de autenticação: o servidor precisa de credenciais válidas, e um novo login pode resolver. O 403 é de autorização: o servidor sabe quem você é, ou nem precisa saber, e mesmo assim recusa, então logar de novo não adianta.
- Por que recebo 401 com um token que funcionava há um minuto?
- Access tokens têm vida curta, muitas vezes de 5 a 60 minutos. Decodifique o JWT e confira a claim exp. Relógios dessincronizados entre o emissor e a API também fazem o token parecer vencido antes da hora.
- Por que minha API dá 401 só quando chamo pelo navegador?
- Quase sempre é o preflight de CORS. O navegador manda antes um OPTIONS sem o cabeçalho Authorization; se o middleware exige autenticação no OPTIONS, o preflight falha e o pedido real nem sai.
- Posso enviar 401 sem o cabeçalho WWW-Authenticate?
- Não. A RFC 9110 diz que o servidor DEVE enviar o WWW-Authenticate com pelo menos um desafio. Em APIs com token, Bearer realm="api" já basta.
Revisado em por Arielton Oberek.