Sessão, JWT e OAuth2
As três formas mais comuns de identificar o usuário a cada pedido, com as trocas de cada uma.
Em produção, a autenticação quase sempre passa pelo framework ou por um provedor. Os exemplos abaixo usam a biblioteca de JWT mais adotada em cada linguagem, para deixar visíveis as verificações que o framework faz por você e que um código escrito às pressas esquece.
Emitir e validar um JWT
O token leva o identificador do usuário (sub), quem o emitiu (iss), para quem ele vale (aud) e quando vence (exp). A validação confere a assinatura e cada um desses campos, e aceita um único algoritmo.
import { SignJWT, jwtVerify } from "jose";
const segredo = new TextEncoder().encode(process.env.JWT_SECRET);
const EMISSOR = "https://api.loja.com.br";
const PUBLICO = "loja-web";
export async function emitirToken(usuarioId: string) {
return new SignJWT({ papel: "cliente" })
.setProtectedHeader({ alg: "HS256" })
.setSubject(usuarioId)
.setIssuer(EMISSOR)
.setAudience(PUBLICO)
.setIssuedAt()
.setExpirationTime("15m")
.sign(segredo);
}
export async function validarToken(token: string) {
const { payload } = await jwtVerify(token, segredo, {
algorithms: ["HS256"],
issuer: EMISSOR,
audience: PUBLICO,
});
return payload;
}import os
from datetime import datetime, timedelta, timezone
import jwt
SEGREDO = os.environ["JWT_SECRET"]
EMISSOR = "https://api.loja.com.br"
PUBLICO = "loja-web"
def emitir_token(usuario_id: str) -> str:
agora = datetime.now(timezone.utc)
payload = {
"sub": usuario_id,
"papel": "cliente",
"iss": EMISSOR,
"aud": PUBLICO,
"iat": agora,
"exp": agora + timedelta(minutes=15),
}
return jwt.encode(payload, SEGREDO, algorithm="HS256")
def validar_token(token: str) -> dict:
return jwt.decode(
token,
SEGREDO,
algorithms=["HS256"],
issuer=EMISSOR,
audience=PUBLICO,
)package auth
import (
"os"
"time"
"github.com/golang-jwt/jwt/v5"
)
var segredo = []byte(os.Getenv("JWT_SECRET"))
const (
emissor = "https://api.loja.com.br"
publico = "loja-web"
)
func EmitirToken(usuarioID string) (string, error) {
agora := time.Now()
claims := jwt.MapClaims{
"sub": usuarioID,
"papel": "cliente",
"iss": emissor,
"aud": publico,
"iat": agora.Unix(),
"exp": agora.Add(15 * time.Minute).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(segredo)
}
func ValidarToken(bruto string) (jwt.MapClaims, error) {
claims := jwt.MapClaims{}
_, err := jwt.ParseWithClaims(bruto, claims,
func(t *jwt.Token) (any, error) { return segredo, nil },
jwt.WithValidMethods([]string{"HS256"}),
jwt.WithIssuer(emissor),
jwt.WithAudience(publico),
jwt.WithExpirationRequired(),
)
return claims, err
}import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import javax.crypto.SecretKey;
public class Tokens {
private static final String EMISSOR = "https://api.loja.com.br";
private static final String PUBLICO = "loja-web";
private static final SecretKey CHAVE = Keys.hmacShaKeyFor(
System.getenv("JWT_SECRET").getBytes(StandardCharsets.UTF_8));
public static String emitir(String usuarioId) {
Instant agora = Instant.now();
return Jwts.builder()
.subject(usuarioId)
.claim("papel", "cliente")
.issuer(EMISSOR)
.audience().add(PUBLICO).and()
.issuedAt(Date.from(agora))
.expiration(Date.from(agora.plus(15, ChronoUnit.MINUTES)))
.signWith(CHAVE)
.compact();
}
public static Claims validar(String token) {
return Jwts.parser()
.verifyWith(CHAVE)
.requireIssuer(EMISSOR)
.requireAudience(PUBLICO)
.build()
.parseSignedClaims(token)
.getPayload();
}
}using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
public class Tokens
{
private const string Emissor = "https://api.loja.com.br";
private const string Publico = "loja-web";
private static readonly SymmetricSecurityKey Chave = new(
Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("JWT_SECRET")!));
private static readonly JsonWebTokenHandler Handler = new();
public static string Emitir(string usuarioId) =>
Handler.CreateToken(new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
[
new Claim("sub", usuarioId),
new Claim("papel", "cliente"),
]),
Issuer = Emissor,
Audience = Publico,
Expires = DateTime.UtcNow.AddMinutes(15),
SigningCredentials = new SigningCredentials(
Chave, SecurityAlgorithms.HmacSha256),
});
public static async Task<IDictionary<string, object>> Validar(string token)
{
var resultado = await Handler.ValidateTokenAsync(token,
new TokenValidationParameters
{
ValidIssuer = Emissor,
ValidAudience = Publico,
IssuerSigningKey = Chave,
ValidAlgorithms = [SecurityAlgorithms.HmacSha256],
});
if (!resultado.IsValid)
{
throw resultado.Exception;
}
return resultado.Claims;
}
}O que muda de uma linguagem para outra
| Biblioteca | Exige escolher o algoritmo na validação | Confere exp sozinha | |
|---|---|---|---|
| Node | jose | Não, mas aceitar só um é recomendado | Sim |
| Python | PyJWT | Sim: algorithms é obrigatório | Sim |
| Go | golang-jwt/jwt v5 | Não, mas WithValidMethods é recomendado | Sim quando presente; WithExpirationRequired exige que exista |
| Java | jjwt 0.12 | Não; o algoritmo segue o tipo da chave | Sim |
| C# | Microsoft.IdentityModel | Não, mas ValidAlgorithms é recomendado | Sim |
Em ASP.NET, Spring Security e nos middlewares de Express, FastAPI e Go, essa mesma validação é configurada uma vez, e cada rota recebe o usuário já identificado. Os nomes mudam; os campos conferidos são os mesmos.
HS256 ou RS256
| Algoritmo | Chave | Quem consegue emitir | Quando usar |
|---|---|---|---|
| HS256 | Um segredo compartilhado | Qualquer um que valida | Um único serviço emite e valida |
| RS256 ou ES256 | Par de chaves: privada assina, pública valida | Só quem tem a chave privada | Vários serviços validam, ou o token vem de um provedor |
Com um provedor OIDC, os serviços baixam as chaves públicas do endereço jwks_uri do provedor e validam os tokens sem nenhum segredo compartilhado. As bibliotecas acima aceitam essa configuração e guardam as chaves em cache.
O cookie de sessão
Seja um identificador de sessão ou um token, o cookie que o carrega precisa de quatro atributos:
Set-Cookie: sessao=k3J9xQ2mZ8...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400| Atributo | O que faz | Sem ele |
|---|---|---|
HttpOnly | Esconde o cookie do JavaScript | Um script injetado rouba a sessão |
Secure | Só envia o cookie por HTTPS | O cookie viaja em texto legível numa rede aberta |
SameSite=Lax | Não envia o cookie em pedidos POST vindos de outros sites | Outro site dispara ações em nome do usuário |
Max-Age | Define quando o cookie expira | O cookie dura até o navegador fechar, ou mais |
Duas regras completam o quadro:
- Gere um novo identificador de sessão no login. Se o identificador anterior ao login continuar valendo, quem o conhecia antes herda a sessão autenticada (fixação de sessão).
- No logout, apague no servidor. Remover só o cookie do navegador deixa a sessão válida para quem tiver copiado o valor.
Lista de revisão
Tokens
- A validação confere assinatura,
exp,isseaud, com algoritmo fixo? - O token de acesso vence em minutos? Existe renovação com registro e rotação?
- O segredo tem pelo menos 32 bytes aleatórios e vem do ambiente, fora do código?
- O token não carrega dado sensível?
Sessões e cookies
- O cookie tem
HttpOnly,Secure,SameSitee expiração? - O identificador da sessão é trocado no login?
- O logout apaga a sessão no servidor?
- Trocar a senha encerra as outras sessões do usuário?
Fluxo
- O OAuth2 usa código de autorização com PKCE, e não os fluxos implícito ou de senha?
- O endereço de retorno do OAuth2 é conferido contra uma lista exata?
- As tentativas de login têm limite por conta e por endereço?
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Implementar login com sessão, passo a passo
Quero implementar um login com sessão do zero, para entender o que os frameworks fazem por mim. Pergunte em qual linguagem e framework eu quero trabalhar. Vamos em etapas. Em cada uma, explique o objetivo, deixe que eu tente, e depois mostre a sua versão comentando as diferenças: 1. Cadastro com a senha guardada com um algoritmo próprio para senhas. 2. Login que confere a senha, cria a sessão num armazenamento e devolve o cookie com os atributos corretos. 3. Um middleware que identifica o usuário em cada pedido pelo cookie. 4. Troca do identificador da sessão no login. 5. Logout que apaga a sessão no servidor, e encerramento das outras sessões quando a senha muda. 6. Limite de tentativas de login por conta e por endereço. Em cada etapa, mostre como testar com curl, incluindo os casos de ataque, e o que observar nos cabeçalhos.
Validar tokens de um provedor OIDC
Quero aprender a validar no meu backend os tokens emitidos por um provedor OpenID Connect. Pergunte qual linguagem, framework e provedor eu uso. Explique, com código para o meu caso: 1. O que é o documento de descoberta do provedor e o endereço jwks_uri. 2. Como validar a assinatura com as chaves públicas, mantendo as chaves em cache e lidando com a troca de chaves pelo provedor. 3. Quais campos conferir: iss, aud, exp, nbf e, no token de identidade, nonce. 4. A diferença entre token de acesso e token de identidade, e por que a API deve aceitar só o token de acesso. 5. Como extrair o usuário e as permissões do token e entregá-los às rotas. Depois, me mostre cinco tokens com um defeito cada (vencido, emissor errado, público errado, algoritmo inesperado, assinatura alterada) e pergunte o que a minha validação deve fazer com cada um.
Revisar o código de autenticação
Vou colar abaixo código de autenticação: login, emissão ou validação de token, middleware ou configuração de cookies. Revise com foco em segurança. Verifique, nesta ordem, e para cada item diga se está correto, incorreto ou ausente, citando a linha: 1. A senha é guardada com um algoritmo próprio para senhas e comparada de forma segura? 2. O token é validado, e não apenas decodificado? O algoritmo é fixo? São conferidos exp, iss e aud? 3. O segredo ou a chave vem do ambiente e tem tamanho adequado? 4. Onde o token ou a sessão fica guardado no navegador? O cookie tem HttpOnly, Secure, SameSite e expiração? 5. O identificador de sessão é trocado no login? O logout apaga a sessão no servidor? 6. Há limite de tentativas de login? As mensagens de erro revelam se o e-mail existe? 7. Há dado sensível dentro do token ou em logs? Para cada problema, descreva o ataque possível e mostre a correção. Não reescreva o que já está correto. Código: [cole aqui]