Autenticação e autorização em Go parecem assuntos resolvidos até a primeira API crescer. No começo, um token fixo no header ou um middleware simples parecem suficientes. Depois chegam área administrativa, usuários de empresas diferentes, permissões por plano, integrações externas, auditoria, rotação de chaves, sessão em múltiplos dispositivos e endpoints que não podem vazar dados entre tenants. Nesse ponto, “verificar se tem token” deixa de ser segurança e vira apenas uma barreira frágil.
Go é uma ótima linguagem para APIs por causa da simplicidade do net/http, da boa performance em concorrência e da facilidade de compilar serviços pequenos. Essa simplicidade, porém, cobra uma escolha consciente de arquitetura. A linguagem não força um framework de autenticação. Você precisa decidir como identificar o usuário, como representar permissões, onde validar políticas e como testar os caminhos de erro.
Este guia mostra como pensar em autenticação e autorização em APIs Go: diferença entre os dois conceitos, quando usar sessão ou JWT, como escrever middleware HTTP, como modelar RBAC sem exagero e quais cuidados importam em produção. Se você ainda está montando a base da aplicação, leia também API REST em Go, Go Security e o guia de rate limiting em Go.
Autenticação não é autorização
Autenticação responde: “quem está fazendo esta requisição?”. Autorização responde: “essa pessoa pode fazer isto agora?”. Misturar as duas coisas é uma das fontes mais comuns de bugs de segurança em APIs.
Um usuário autenticado pode estar logado corretamente e ainda assim não ter permissão para acessar um recurso. Um analista pode ler relatórios, mas não alterar cobrança. Um administrador de uma empresa pode gerenciar usuários daquela empresa, mas não de outro tenant. Um token de integração pode criar pedidos, mas não listar dados pessoais.
Na prática, separe o fluxo mental em três camadas:
- Identidade: descobrir
user_id,account_id,tenant_id, tipo de cliente e origem da requisição. - Credencial: validar senha, sessão, cookie, token de API, JWT, OAuth2 ou mTLS.
- Política: decidir se aquela identidade tem permissão para executar a ação no recurso solicitado.
Essa separação evita que o middleware de login vire um bloco gigante que conhece todas as regras de negócio. O middleware deve autenticar e anexar identidade ao contexto. A camada de autorização deve tomar decisões mais perto do caso de uso.
Sessão, JWT ou token de API?
Não existe uma resposta universal. O formato certo depende do produto.
Sessões com cookie HTTP funcionam muito bem para aplicações web tradicionais. O servidor guarda o estado da sessão em banco, Redis ou store similar, e o navegador envia um cookie seguro a cada requisição. A vantagem é controle: você consegue revogar sessão, invalidar dispositivos e aplicar políticas server-side. Para painéis administrativos, SaaS B2B e produtos com login humano, essa abordagem continua excelente.
JWT é útil quando você precisa carregar claims assinadas entre serviços ou integrar com um provedor de identidade. O cuidado é não transformar JWT em “sessão impossível de revogar”. Tokens longos demais, sem rotação, sem aud, sem iss e sem validação de algoritmo viram risco. Em APIs públicas, combine access token curto com refresh token protegido.
Tokens de API são bons para integrações máquina-a-máquina. Eles devem ter escopo, dono, data de criação, último uso, prefixo identificável e possibilidade de revogação. Nunca armazene o token puro no banco. Guarde hash, mostre o segredo apenas uma vez e registre auditoria de uso.
Uma regra prática para times Go:
- Web app com navegador: sessão + cookie
HttpOnly,SecureeSameSite. - API consumida por frontend próprio: sessão ou access token curto, dependendo da arquitetura.
- API pública para clientes: token de API com escopos e rate limit por chave.
- SSO corporativo: OAuth2/OIDC com provedor externo e validação cuidadosa de claims.
- Comunicação entre serviços internos: identidade de workload, mTLS ou token curto emitido por infraestrutura confiável.
Middleware de autenticação em Go
Um middleware Go não precisa ser complicado. Ele recebe um http.Handler, valida a credencial e chama o próximo handler com dados no context.Context.
package auth
import (
"context"
"net/http"
"strings"
)
type contextKey string
const userContextKey contextKey = "auth_user"
type User struct {
ID string
TenantID string
Roles []string
}
type TokenVerifier interface {
Verify(token string) (*User, error)
}
func Middleware(verifier TokenVerifier) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
if header == "" || !strings.HasPrefix(header, "Bearer ") {
http.Error(w, "não autenticado", http.StatusUnauthorized)
return
}
token := strings.TrimPrefix(header, "Bearer ")
user, err := verifier.Verify(token)
if err != nil {
http.Error(w, "token inválido", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userContextKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func UserFromContext(ctx context.Context) (*User, bool) {
user, ok := ctx.Value(userContextKey).(*User)
return user, ok
}
O exemplo é simples de propósito. Em produção, você provavelmente vai querer respostas JSON padronizadas, logs com slog, métricas, tracing e distinção entre credencial ausente, expirada e malformada. Mas a estrutura central permanece: validar credencial, construir identidade e seguir para a regra de negócio.
Evite colocar senha, token ou claims sensíveis no log. Quando precisar debugar, registre identificadores seguros como user_id, tenant_id, token_id ou prefixo público da chave. Para isso, o guia de slog em Go ajuda a manter logs úteis sem vazar segredo.
Autorização perto do caso de uso
O erro comum é checar apenas papel global no middleware: “se é admin, passa”. Isso quebra quando o produto cresce. A pessoa pode ser admin de uma organização, mas não de outra. Pode editar um projeto, mas não faturamento. Pode ver dados agregados, mas não exportar dados pessoais.
Prefira funções explícitas no serviço:
func CanUpdateProject(user *auth.User, project Project) bool {
if user.TenantID != project.TenantID {
return false
}
return hasRole(user, "admin") || hasRole(user, "project_manager")
}
Essa checagem parece menos “sofisticada” que um framework de policy, mas é legível, testável e difícil de contornar. Para produtos maiores, você pode evoluir para uma camada de policy dedicada, usando RBAC, ABAC ou ferramentas como OPA. O importante é manter a decisão explícita e coberta por testes.
Alguns princípios úteis:
- Sempre cheque tenant antes de papel. Multi-tenant bug costuma ser mais grave que falta de tela bonita.
- Não confie em
user_idvindo do corpo da requisição para decidir escopo. - Busque o recurso no banco antes de autorizar ações específicas.
- Trate leitura, criação, edição, exclusão e exportação como permissões diferentes.
- Faça autorização no backend mesmo quando o frontend esconde botões.
JWT com cuidado
JWT não é ruim. O problema é usar JWT como desculpa para não pensar em ciclo de vida. Uma validação mínima deve checar assinatura, algoritmo esperado, expiração, emissor, audiência e claims obrigatórias.
Também vale decidir o que não entra no token. Dados que mudam com frequência, como permissões detalhadas, status de pagamento ou bloqueio de conta, podem ficar desatualizados. Se a autorização depende desses campos, consulte uma fonte server-side ou use tokens de vida curta.
Checklist para JWT em Go:
- Use biblioteca madura e mantenha dependências atualizadas.
- Rejeite algoritmo inesperado; não aceite downgrade para
none. - Valide
exp,nbf,iat,isseaudquando aplicável. - Use
kidpara rotação de chaves, mas não confie cegamente em URL externa sem controle. - Mantenha access tokens curtos e refresh tokens protegidos.
- Registre
jtiou identificador equivalente se precisar revogar tokens específicos.
Se a API roda em múltiplas réplicas, pense também em cache de chaves públicas, fallback quando o provedor de identidade falha e observabilidade de erros por causa. Autenticação é dependência crítica; quando ela degrada, o usuário percebe como indisponibilidade.
Cookies seguros para APIs com navegador
Para aplicações web, cookies continuam sendo uma opção forte quando configurados corretamente. O backend controla a sessão, o navegador cuida do envio automático e você evita guardar access token em localStorage.
Configurações básicas:
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sessionID,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
HttpOnly reduz exposição a XSS porque JavaScript não consegue ler o cookie. Secure exige HTTPS. SameSite ajuda contra CSRF, mas não substitui análise de risco. Para ações sensíveis, use token CSRF, reautenticação ou confirmação adicional.
Se você tem frontend separado em outro domínio, revise CORS com cuidado. Liberar Access-Control-Allow-Origin: * junto com credenciais é erro clássico. Prefira lista explícita de origens, métodos e headers.
Testes para fluxos de auth
Autenticação e autorização precisam de testes de negação, não só do caminho feliz. É comum testar “admin consegue editar” e esquecer “usuário de outro tenant não consegue”. Em segurança, os casos negativos são o produto.
Com httptest, você consegue exercitar middleware e handler juntos:
func TestUpdateProjectRejectsOtherTenant(t *testing.T) {
verifier := fakeVerifier{
user: &auth.User{ID: "u1", TenantID: "tenant-a", Roles: []string{"admin"}},
}
handler := auth.Middleware(verifier)(updateProjectHandler(projectFromTenantB))
req := httptest.NewRequest(http.MethodPatch, "/projects/p1", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
}
}
Monte uma tabela de testes para papéis, tenants, escopos e ações. O artigo sobre testes de tabela em Go mostra como transformar muitos cenários parecidos em uma suíte legível. Inclua também testes para token ausente, token expirado, assinatura inválida, usuário bloqueado, recurso inexistente e tentativa de acessar recurso de outro tenant.
Observabilidade e auditoria
Auth sem observabilidade vira caixa-preta. Você precisa saber se aumentaram falhas de login, tokens expirados, tentativas de acesso negado, erros de provedor OIDC, criação de tokens de API e uso de permissões administrativas.
Registre eventos de auditoria para ações sensíveis:
- Login bem-sucedido e falho.
- Troca de senha, MFA e recuperação de conta.
- Criação, rotação e revogação de token de API.
- Mudança de papel ou permissão.
- Exportação de dados.
- Acesso negado a recurso crítico.
Não confunda auditoria com log de debug. Auditoria precisa ser estruturada, preservável e consultável por incidente. Logs de debug podem ter retenção menor. Métricas, por outro lado, ajudam a detectar comportamento anormal: aumento de 401, pico de 403, muitos logins falhos por IP, ou muitas chamadas negadas para uma única conta.
Combine isso com observabilidade em Go e rate limiting. Login, recuperação de senha e criação de token são endpoints que merecem limites próprios.
Erros comuns em APIs Go
Alguns problemas aparecem repetidamente em reviews:
- Validar autenticação no frontend e esquecer o backend.
- Usar o mesmo segredo JWT em todos os ambientes.
- Guardar token de API puro no banco.
- Colocar dados sensíveis em claims, logs ou mensagens de erro.
- Retornar
404e403sem critério, criando enumeração de recursos em algumas rotas e ocultação em outras. - Misturar autorização de tenant com role global.
- Deixar endpoints internos sem proteção porque “só roda na rede privada”.
- Não testar revogação, expiração e usuário bloqueado.
O melhor desenho é aquele que uma pessoa nova no time consegue auditar. Em Go, isso combina bem com handlers pequenos, serviços explícitos, interfaces simples para dependências externas e testes de tabela cobrindo permissões.
O que levar para produção
Para uma API real, comece com uma matriz simples:
| Área | Decisão mínima |
|---|---|
| Identidade | user_id, tenant_id, roles e token id no contexto |
| Credencial | sessão, JWT, OAuth2 ou token de API conforme o canal |
| Autorização | função explícita por ação sensível |
| Auditoria | evento estruturado para ações críticas |
| Testes | casos positivos e negativos por papel, tenant e recurso |
| Operação | métricas de 401, 403, login falho e provedor externo |
Não tente resolver todos os modelos de permissão no primeiro commit. Comece com regras claras para o produto atual, mas deixe separação suficiente para evoluir. Um middleware que autentica, uma camada que autoriza e testes que provam negação já colocam a API em patamar muito melhor que uma coleção de if user.IsAdmin espalhados.
Esse tema também aparece com frequência em vagas backend brasileiras. Muitas descrições pedem Go junto com APIs REST, segurança, OAuth2, JWT, Kubernetes, observabilidade e cloud. Depois de revisar este guia, compare com vagas Go no Brasil e com o material de entrevista técnica Go. Para quem acompanha múltiplas stacks, o guia de APIs REST com FastAPI no Python Dev Brasil ajuda a contrastar a experiência de auth em frameworks mais opinativos com a abordagem explícita e enxuta de Go.
Autenticação e autorização não precisam ser mágicas. Em Go, o caminho sustentável é deixar identidade, credencial e política visíveis no código, com limites bem definidos e testes que negam acesso quando devem negar. É menos glamouroso que trocar de framework, mas é exatamente o tipo de disciplina que mantém APIs brasileiras seguras quando produto, time e tráfego crescem.