<h2>Entendendo JWT: Fundamentos e Estrutura</h2>
<p>JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define um método compacto e autossuficiente para transmitir informações entre partes de forma segura. Um JWT é composto por três partes separadas por pontos: <strong>header</strong>, <strong>payload</strong> e <strong>signature</strong>. Quando você vê um token como <code>eyJhbGc...eyJzdWI...SflKxw...</code>, cada seção representa um segmento diferente, todos codificados em Base64URL.</p>
<p>A beleza do JWT está em sua natureza stateless. Diferentemente de sessões tradicionais, o servidor não precisa armazenar informações sobre o token no banco de dados. Tudo que o servidor precisa saber está contido no próprio token, assinado criptograficamente para garantir que não foi alterado. Isso torna JWTs ideais para APIs escaláveis, especialmente em arquiteturas de microsserviços onde múltiplos servidores precisam validar credenciais sem compartilhar estado.</p>
<h3>Estrutura e Componentes</h3>
<p>O <strong>header</strong> contém informações sobre o tipo de token e o algoritmo de assinatura usado. Por exemplo: <code>{"alg":"HS256","typ":"JWT"}</code>. O <strong>payload</strong> (também chamado de claims) carrega os dados reais, como ID do usuário, email e permissões. O <strong>signature</strong> é gerado combinando o header e payload codificados com uma chave secreta, garantindo integridade. Nenhuma dessas partes é criptografada — apenas assinada — então nunca coloque informações sensíveis como senhas no payload.</p>
<h2>Implementando JWT em Go: Geração de Tokens</h2>
<p>Para trabalhar com JWT em Go, usaremos a biblioteca <code>github.com/golang-jwt/jwt/v5</code>, que é a sucessora mantida do projeto original jwt-go. Ela oferece uma API limpa e suporta todos os algoritmos de assinatura principais. Vamos criar uma estrutura de autenticação robusta do zero.</p>
<h3>Configuração Inicial e Tipos</h3>
<p>Primeiro, definiremos os tipos e configurações que usaremos em toda a aplicação:</p>
<pre><code class="language-go">package auth
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
// Config mantém as configurações de JWT
type Config struct {
SecretKey string
AccessDuration time.Duration
RefreshDuration time.Duration
}
// Claims define as informações contidas no JWT
type Claims struct {
UserID int json:"user_id"
Email string json:"email"
Username string json:"username"
Role string json:"role"
jwt.RegisteredClaims
}
// TokenResponse é o retorno quando geramos um token
type TokenResponse struct {
AccessToken string json:"access_token"
RefreshToken string json:"refresh_token"
ExpiresIn int64 json:"expires_in"
TokenType string json:"token_type"
}
var ErrInvalidToken = errors.New("token inválido ou expirado")
var ErrExpiredToken = errors.New("token expirado")</code></pre>
<h3>Gerando Access Tokens</h3>
<p>Agora implementaremos a função que gera o access token. Este token é de curta duração (tipicamente 15 minutos) e contém os dados do usuário:</p>
<pre><code class="language-go">package auth
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// Manager gerencia operações com JWT
type Manager struct {
config Config
}
// NewManager cria uma nova instância do gerenciador
func NewManager(config Config) *Manager {
return &Manager{config: config}
}
// GenerateAccessToken cria um novo access token
func (m *Manager) GenerateAccessToken(userID int, email, username, role string) (string, error) {
now := time.Now()
expiresAt := now.Add(m.config.AccessDuration)
claims := Claims{
UserID: userID,
Email: email,
Username: username,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresAt),
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
Issuer: "api-app",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(m.config.SecretKey))
if err != nil {
return "", fmt.Errorf("erro ao assinar token: %w", err)
}
return tokenString, nil
}</code></pre>
<p>O <code>RegisteredClaims</code> do JWT contém campos padrão como <code>ExpiresAt</code> (quando expira), <code>IssuedAt</code> (quando foi criado) e <code>Issuer</code> (quem emitiu). O algoritmo HS256 (HMAC SHA256) é simétrico — a mesma chave secreta assina e valida.</p>
<h2>Validação e Parsing de Tokens</h2>
<p>Validar um token é tão importante quanto gerá-lo. Precisamos garantir que o token não foi falsificado e que ainda está válido. Go fornece ferramentas robustas para isso através do método <code>ParseWithClaims</code>.</p>
<h3>Função de Validação Completa</h3>
<pre><code class="language-go">package auth
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
// ValidateToken valida e extrai claims de um token
func (m Manager) ValidateToken(tokenString string) (Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(
tokenString,
claims,
func(token *jwt.Token) (interface{}, error) {
// Verificar se o algoritmo é o esperado (prevenção contra 'none' algorithm attack)
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("método de assinatura inesperado: %v", token.Header["alg"])
}
return []byte(m.config.SecretKey), nil
},
)
if err != nil {
return nil, fmt.Errorf("erro ao fazer parse do token: %w", err)
}
if !token.Valid {
return nil, ErrInvalidToken
}
// Verificar expiração explicitamente (ParseWithClaims já faz, mas ser explícito é bom)
if claims.ExpiresAt != nil && claims.ExpiresAt.Before(time.Now()) {
return nil, ErrExpiredToken
}
return claims, nil
}</code></pre>
<p>A verificação <code>if _, ok := token.Method.(*jwt.SigningMethodHMAC)</code> é crucial para segurança. Protege contra o ataque "algorithm substitution" onde um atacante tenta usar <code>alg: "none"</code> para pular a validação. Assim garantimos que apenas HMAC é aceito.</p>
<h3>Middleware de Autenticação HTTP</h3>
<p>Em uma API real, você precisará validar tokens em cada requisição. Aqui está um middleware padrão:</p>
<pre><code class="language-go">package auth
import (
"net/http"
"strings"
)
// AuthMiddleware valida o JWT em requisições HTTP
func (m *Manager) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extrair o token do header Authorization
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "header Authorization ausente", http.StatusUnauthorized)
return
}
// Bearer token format: "Bearer <token>"
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "formato de Authorization inválido", http.StatusUnauthorized)
return
}
tokenString := parts[1]
claims, err := m.ValidateToken(tokenString)
if err != nil {
http.Error(w, "token inválido: "+err.Error(), http.StatusUnauthorized)
return
}
// Você pode armazenar claims no contexto para uso posterior
// ctx := context.WithValue(r.Context(), "claims", claims)
// next.ServeHTTP(w, r.WithContext(ctx))
next.ServeHTTP(w, r)
})
}</code></pre>
<h2>Refresh Tokens e Rotação de Credenciais</h2>
<p>Access tokens de curta duração melhoram a segurança, mas exigem que usuários façam login novamente frequentemente, prejudicando a experiência. A solução é usar <strong>refresh tokens</strong> — tokens de longa duração que só servem para obter novos access tokens. Esse padrão permite que o access token seja invalidado no servidor sem deslogar o usuário.</p>
<h3>Gerando Refresh Tokens</h3>
<p>Um refresh token é gerado junto com o access token, tem duração muito maior (dias ou semanas) e não contém dados sensíveis. Você deve armazená-lo no banco de dados para poder revogá-lo:</p>
<pre><code class="language-go">package auth
import (
"crypto/rand"
"encoding/hex"
"fmt"
"time"
)
// RefreshTokenData estrutura para armazenar refresh tokens no BD
type RefreshTokenData struct {
ID string db:"id"
UserID int db:"user_id"
Token string db:"token"
ExpiresAt time.Time db:"expires_at"
CreatedAt time.Time db:"created_at"
Revoked bool db:"revoked"
}
// GenerateTokenPair cria um access token e um refresh token
func (m Manager) GenerateTokenPair(userID int, email, username, role string) (TokenResponse, error) {
// Gerar access token
accessToken, err := m.GenerateAccessToken(userID, email, username, role)
if err != nil {
return nil, err
}
// Gerar refresh token (string aleatória segura)
refreshToken, err := generateRandomToken(32)
if err != nil {
return nil, fmt.Errorf("erro ao gerar refresh token: %w", err)
}
expiresIn := int64(m.config.AccessDuration.Seconds())
return &TokenResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: expiresIn,
TokenType: "Bearer",
}, nil
}
// generateRandomToken cria um token seguro aleatório
func generateRandomToken(length int) (string, error) {
b := make([]byte, length)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}</code></pre>
<h3>Validação e Renovação de Refresh Tokens</h3>
<p>O refresh token deve ser validado no banco de dados (verificar se foi revogado) e então um novo access token é emitido:</p>
<pre><code class="language-go">package auth
import (
"database/sql"
"fmt"
"time"
)
// TokenStore interface para operações com tokens no BD (você implementa)
type TokenStore interface {
SaveRefreshToken(data *RefreshTokenData) error
GetRefreshToken(token string) (*RefreshTokenData, error)
RevokeRefreshToken(token string) error
DeleteExpiredTokens() error
}
// RefreshAccessToken valida um refresh token e emite um novo access token
func (m *Manager) RefreshAccessToken(refreshToken string, store TokenStore) (string, error) {
// Buscar no banco de dados
tokenData, err := store.GetRefreshToken(refreshToken)
if err != nil {
if err == sql.ErrNoRows {
return "", ErrInvalidToken
}
return "", fmt.Errorf("erro ao buscar refresh token: %w", err)
}
// Verificar se foi revogado
if tokenData.Revoked {
return "", fmt.Errorf("refresh token foi revogado")
}
// Verificar expiração
if time.Now().After(tokenData.ExpiresAt) {
return "", ErrExpiredToken
}
// Emitir novo access token
newAccessToken, err := m.GenerateAccessToken(
tokenData.UserID,
"", // você buscaria email/username do BD
"",
"",
)
if err != nil {
return "", err
}
return newAccessToken, nil
}
// RevokeToken marca um refresh token como revogado (logout)
func (m *Manager) RevokeToken(refreshToken string, store TokenStore) error {
return store.RevokeRefreshToken(refreshToken)
}</code></pre>
<h3>Exemplo de Implementação do TokenStore com SQLite</h3>
<p>Para completar o quadro, aqui está uma implementação real com SQLite:</p>
<pre><code class="language-go">package auth
import (
"database/sql"
"time"
_ "github.com/mattn/go-sqlite3"
)
type SQLiteTokenStore struct {
db *sql.DB
}
func NewSQLiteTokenStore(dbPath string) (*SQLiteTokenStore, error) {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, err
}
// Criar tabela se não existir
schema := `
CREATE TABLE IF NOT EXISTS refresh_tokens (
id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
revoked BOOLEAN DEFAULT FALSE
);
`
if _, err := db.Exec(schema); err != nil {
return nil, err
}
return &SQLiteTokenStore{db: db}, nil
}
func (s SQLiteTokenStore) SaveRefreshToken(data RefreshTokenData) error {
_, err := s.db.Exec(
`INSERT INTO refresh_tokens (id, user_id, token, expires_at, created_at, revoked)
VALUES (?, ?, ?, ?, ?, ?)`,
data.ID, data.UserID, data.Token, data.ExpiresAt, data.CreatedAt, data.Revoked,
)
return err
}
func (s SQLiteTokenStore) GetRefreshToken(token string) (RefreshTokenData, error) {
data := &RefreshTokenData{}
err := s.db.QueryRow(
`SELECT id, user_id, token, expires_at, created_at, revoked
FROM refresh_tokens WHERE token = ?`,
token,
).Scan(&data.ID, &data.UserID, &data.Token, &data.ExpiresAt, &data.CreatedAt, &data.Revoked)
if err != nil {
return nil, err
}
return data, nil
}
func (s *SQLiteTokenStore) RevokeRefreshToken(token string) error {
_, err := s.db.Exec(
UPDATE refresh_tokens SET revoked = TRUE WHERE token = ?,
token,
)
return err
}
func (s *SQLiteTokenStore) DeleteExpiredTokens() error {
_, err := s.db.Exec(
DELETE FROM refresh_tokens WHERE expires_at < ?,
time.Now(),
)
return err
}</code></pre>
<h2>Fluxo Completo: Autenticação com Login e Refresh</h2>
<p>Colocando tudo junto, aqui está um fluxo realista de autenticação em uma API HTTP:</p>
<h3>Handlers de Login e Refresh</h3>
<pre><code class="language-go">package handlers
import (
"encoding/json"
"net/http"
"your-module/auth"
"crypto/sha256"
"encoding/hex"
)
type LoginRequest struct {
Email string json:"email"
Password string json:"password"
}
type RefreshRequest struct {
RefreshToken string json:"refresh_token"
}
type AuthHandler struct {
authManager *auth.Manager
tokenStore auth.TokenStore
userStore UserStore // você implementa
}
// UserStore interface (exemplo)
type UserStore interface {
GetUserByEmail(email string) (*User, error)
}
type User struct {
ID int
Email string
Username string
Password string // hash
Role string
}
// HandleLogin autentica usuário e retorna token pair
func (h AuthHandler) HandleLogin(w http.ResponseWriter, r http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "método não permitido", http.StatusMethodNotAllowed)
return
}
var req LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "request inválido", http.StatusBadRequest)
return
}
// Buscar usuário
user, err := h.userStore.GetUserByEmail(req.Email)
if err != nil {
http.Error(w, "credenciais inválidas", http.StatusUnauthorized)
return
}
// Verificar senha (exemplo simplificado)
if !verifyPassword(req.Password, user.Password) {
http.Error(w, "credenciais inválidas", http.StatusUnauthorized)
return
}
// Gerar tokens
tokenPair, err := h.authManager.GenerateTokenPair(user.ID, user.Email, user.Username, user.Role)
if err != nil {
http.Error(w, "erro ao gerar tokens", http.StatusInternalServerError)
return
}
// Salvar refresh token no BD
tokenData := &auth.RefreshTokenData{
ID: generateID(),
UserID: user.ID,
Token: tokenPair.RefreshToken,
ExpiresAt: time.Now().AddDate(0, 0, 7), // 7 dias
CreatedAt: time.Now(),
Revoked: false,
}
if err := h.tokenStore.SaveRefreshToken(tokenData); err != nil {
http.Error(w, "erro ao salvar token", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tokenPair)
}
// HandleRefresh renova o access token usando refresh token
func (h AuthHandler) HandleRefresh(w http.ResponseWriter, r http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "método não permitido", http.StatusMethodNotAllowed)
return
}
var req RefreshRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "request inválido", http.StatusBadRequest)
return
}
// Renovar access token
newAccessToken, err := h.authManager.RefreshAccessToken(req.RefreshToken, h.tokenStore)
if err != nil {
http.Error(w, "refresh token inválido: "+err.Error(), http.StatusUnauthorized)
return
}
response := map[string]string{
"access_token": newAccessToken,
"token_type": "Bearer",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// HandleLogout revoga o refresh token
func (h AuthHandler) HandleLogout(w http.ResponseWriter, r http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "método não permitido", http.StatusMethodNotAllowed)
return
}
var req RefreshRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "request inválido", http.StatusBadRequest)
return
}
if err := h.authManager.RevokeToken(req.RefreshToken, h.tokenStore); err != nil {
http.Error(w, "erro ao logout", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"message": "logout realizado"})
}
// verifyPassword compara senha com hash (use bcrypt em produção!)
func verifyPassword(password, hash string) bool {
// EXEMPLO: não use SHA256 em produção! Use bcrypt.CompareHashAndPassword
h := sha256.Sum256([]byte(password))
return hex.EncodeToString(h[:]) == hash
}
func generateID() string {
// Implementar geração de ID único
return "id-" + time.Now().Format("20060102150405")
}</code></pre>
<h3>Configuração e Uso na Aplicação Principal</h3>
<pre><code class="language-go">package main
import (
"net/http"
"time"
"your-module/auth"
"your-module/handlers"
)
func main() {
// Configurar JWT
jwtConfig := auth.Config{
SecretKey: "sua-chave-secreta-muito-segura-32-caracteres", // Use variável de ambiente!
AccessDuration: 15 * time.Minute,
RefreshDuration: 7 24 time.Hour,
}
authManager := auth.NewManager(jwtConfig)
tokenStore, _ := auth.NewSQLiteTokenStore("./tokens.db")
// Criar handler de autenticação
authHandler := &handlers.AuthHandler{
authManager: authManager,
tokenStore: tokenStore,
// userStore: seu userStore implementation
}
// Rotas públicas (sem autenticação)
http.HandleFunc("/login", authHandler.HandleLogin)
http.HandleFunc("/refresh", authHandler.HandleRefresh)
http.HandleFunc("/logout", authHandler.HandleLogout)
// Rotas protegidas (com middleware)
protectedMux := http.NewServeMux()
protectedMux.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Aqui você pode acessar as claims do contexto
w.Write([]byte({"message": "dados protegidos"}))
})
// Aplicar middleware a rotas protegidas
http.Handle("/profile", authManager.AuthMiddleware(protectedMux))
http.ListenAndServe(":8080", nil)
}</code></pre>
<h2>Conclusão</h2>
<p>Implementar autenticação JWT em Go requer entender três pilares: a <strong>estrutura e geração de tokens</strong> (combinando header, payload e signature com uma chave secreta), a <strong>validação robusta</strong> que protege contra ataques como algorithm substitution, e o <strong>padrão refresh token</strong> que equilibra segurança com experiência do usuário. Use sempre bibliotecas mantidas como <code>golang-jwt/jwt/v5</code>, nunca implemente criptografia manualmente, e armazene refresh tokens no banco de dados para permitir revogação — uma prática crucial para logout real e segurança em compromisso de credenciais.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://pkg.go.dev/github.com/golang-jwt/jwt/v5" target="_blank" rel="noopener noreferrer">golang-jwt/jwt Documentation</a> — Documentação oficial da biblioteca JWT para Go</li>
<li><a href="https://tools.ietf.org/html/rfc7519" target="_blank" rel="noopener noreferrer">RFC 7519 - JSON Web Token (JWT)</a> — Especificação padrão de JWT</li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP JWT Security Best Practices</a> — Guia de segurança JWT</li>
<li><a href="https://golang.org/doc/codewalk/functions" target="_blank" rel="noopener noreferrer">Go Security in Practice</a> — Best practices de segurança em Go</li>
<li><a href="https://go.dev/blog/" target="_blank" rel="noopener noreferrer">The Go Programming Language - Authentication Patterns</a> — Blog oficial com padrões de autenticação</li>
</ul>
<p><!-- FIM --></p>