Por que autenticação em Go não é brincadeira?
\n\nGo — uma linguagem escolhida por sua velocidade e confiabilidade. Mas até o servidor mais rápido se transforma em abóbora se for possível roubar senhas através dele. Autenticação segura em aplicações Go não é apenas "instalei uma biblioteca e esqueci". É toda uma ciência: como armazenar senhas, como não falhar com JWT, como se proteger contra CSRF quando tudo já está pegando fogo.
\n\nNeste artigo, não vamos dar aulas teóricas. Vamos escrever código. Real, funcional, que pode ser inserido diretamente no seu projeto. Abordaremos os três pilares da segurança: hash de senhas (bcrypt), trabalho com tokens JWT e proteção contra falsificação de solicitação entre sites (CSRF).
\n\nFazendo hash de senhas corretamente: por que md5 é maligno
\n\nO primeiro passo para a segurança — nunca, ouça bem, nunca armazene senhas em texto puro. Mesmo que você confie no seu banco de dados. Uma invasão pode acontecer com qualquer um (pergunte ao Yahoo ou Adobe). O hash correto é seu escudo.
\n\nEm Go, a escolha padrão é golang.org/x/crypto/bcrypt. Por que bcrypt? Ele é resistente a ataques de força bruta (inclui "salt" e desacelera os cálculos). MD5 ou SHA-1 são calculados em microssegundos. Bcrypt — em centenas de milissegundos. Para um invasor, é um desastre; para o usuário, imperceptível.
Exemplo: registro de usuário
\n\npackage main\n\nimport (\n "fmt"\n "golang.org/x/crypto/bcrypt"\n)\n\nfunc hashPassword(password string) (string, error) {\n bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n return string(bytes), err\n}\n\nfunc checkPasswordHash(password, hash string) bool {\n err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))\n return err == nil\n}\n\nfunc main() {\n password := "mySecretPass123"\n hash, _ := hashPassword(password)\n\n fmt.Println("Senha:", password)\n fmt.Println("Hash: ", hash)\n\n match := checkPasswordHash(password, hash)\n fmt.Println("Corresponde:", match)\n}\n\nObserve: bcrypt.DefaultCost são 10 iterações. Para produção, pode aumentar para 12-14, mas não exagere, senão o servidor pode cair sob carga. Teste no seu hardware.
Verificação de senha no login
\n\nSimples: pegamos a senha do formulário, pegamos o hash do banco de dados, comparamos via bcrypt.CompareHashAndPassword. Se retornar nil — o usuário é legítimo. Se houver erro — enviamos 401.
func loginHandler(w http.ResponseWriter, r *http.Request) {\n // obtemos a senha do formulário\n storedHash := getUserHashFromDB(r.FormValue("email"))\n if !checkPasswordHash(r.FormValue("password"), storedHash) {\n http.Error(w, "Senha inválida", http.StatusUnauthorized)\n return\n }\n // criamos sessão ou token\n}\n\nJWT: autenticação leve com pegadinha
\n\nDepois que o usuário inseriu a senha, é necessário emitir um "passe". Uma das formas populares é JWT (JSON Web Token). É um token compacto que contém dados do usuário e uma assinatura. Mas o diabo está nos detalhes: uma implementação incorreta de JWT pode tornar sua aplicação vulnerável.
\n\nGeração correta de JWT em Go
\n\nUsamos a biblioteca github.com/golang-jwt/jwt/v5. Nunca escreva sua própria criptografia — apenas bibliotecas testadas.
import (\n "time"\n "github.com/golang-jwt/jwt/v5"\n)\n\nvar jwtKey = []byte("my_secret_key_which_must_be_long_and_random")\n\ntype Claims struct {\n Username string `json:"username"`\n jwt.RegisteredClaims\n}\n\nfunc generateToken(username string) (string, error) {\n expirationTime := time.Now().Add(15 * time.Minute)\n claims := &Claims{\n Username: username,\n RegisteredClaims: jwt.RegisteredClaims{\n ExpiresAt: jwt.NewNumericDate(expirationTime),\n },\n }\n\n token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n return token.SignedS