A autenticação do usuário é um assunto complicado e podemos tornar as coisas ainda mais difíceis se não quisermos usar cookies ou JavaScript. A autenticação é uma necessidade para quase todos os serviços da web que vemos, e existem muitas ferramentas e protocolos para ajudar os desenvolvedores no assunto. Usando o cabeçalho www-authenticate, consegui lidar com sessões de usuário sem nenhum código no lado do cliente, apenas algumas alterações no meu backend. A documentação para o padrão está disponível em [1].
O cabeçalho http www-authenticate é uma ferramenta poderosa e faz parte do padrão básico de autenticação http, mas só fiquei ciente disso recentemente. Neste post, vou contar como usei para lidar com sessões de usuários em meu projeto tasker.
Antes de começarmos, deixe-me dar uma breve visão geral da solução.
Basicamente, tudo o que temos que fazer é adicionar o cabeçalho http: WWW-Authenticate em uma resposta com status 401. isso fará com que os navegadores mostrem um diálogo para o usuário solicitar as credenciais. Fazendo isso, o navegador irá automaticamente enviar o cabeçalho Authorization: ... em solicitações.
Isso pode ser feito quando o usuário visita uma página de login ou tenta acessar conteúdo privado. No meu caso, adicionei isso a uma página de login. O código é assim:
func login(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok { // o usuário não está logado (não enviou credenciais) w.Header().Set("WWW-Authenticate", "Basic") w.WriteHeader(http.StatusUnauthorized) return } // verificar credenciais ... // se estiver ok, redireciona o usuário para o conteúdo dele http.Redirect(w, r, "/user.html", http.StatusFound) }
Estou usando o esquema de autorização básica, mas existem muitos outros disponíveis, como digest usando MD5, SHA256, e assim por diante. O RFC 7616 [2] tem todas as informações necessárias.
O fluxo de login que eu projetei é o seguinte:
Quando os navegadores recebem esse cabeçalho na resposta, eles abrem um diálogo para o usuário, alguns aspectos podem ser definidos, por exemplo, se o realm for definido no cabeçalho, ele será exibido para o usuário, mas tenha cuidado, esta opção serve a um propósito, verifique [1] para mais informações. O diálogo se parece com isso no chromium:
Clicar em "entrar" faz com que o navegador repita a solicitação, mas com o cabeçalho Authorization: ..., como eu o configurei para o esquema Básico, o navegador enviará as credenciais codificadas em base64.
A coisa legal sobre isso é que o navegador continuará enviando as credenciais para solicitações subsequentes do mesmo domínio até receber uma resposta de status 401.
Agora que os usuários podem fazer login, toda vez que uma página privada é solicitada, devemos verificar as credenciais. Neste projeto, usei o esquema Básico e o verifico usando as funções http próprias do Go:
usuário, senha, ok := r.BasicAuth() if !ok { w.Header().Set("WWW-Authenticate", "Basic") w.WriteHeader(http.StatusUnauthorized) // enviar uma página de erro ... return } // verifique usuário e senha ... // sirva seu conteúdo
Dessa forma, se uma solicitação chegar não autenticada por algum motivo, o servidor solicitará novamente as credenciais. Outra opção aqui seria redirecionar o usuário para a página de login.
Encerrar a sessão é feito simplesmente retornando um 401 sem o cabeçalho www-authenticate:
func logout(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) // servir uma página HTML de logout ... }
Este é o método que estou usando agora e considero bastante bom: ele usa apenas recursos padrão que existem há anos, nada de novo; não há JavaScript do lado do cliente ou cookies, o que torna fácil de manter e satisfaz até mesmo os usuários mais exigentes.