ADR-0009 Autenticação
Padrão da empresa para autenticação — Better Auth, autoalojado na app, com sessões no nosso Postgres.
- Decisão
- Better Auth 1.7Guardar e proteger
- Estado
- Aceite
- Data
- Âmbito
- Predefinição da empresa para todas as novas apps
Ligações
Apoia-se em
Citado por
- 0004Camada de API
- 0010Limitação de pedidos
- 0013Formulários
- 0014Testes
- 0015Observabilidade
- 0016CI/CD
- 0017Reverse proxy e TLS
- 0025SDK de IA
- 0026Estrutura do projeto
- 0027Princípios de engenharia
- 0028Jobs em segundo plano e agendamento
- 0029Email transacional
- 0030Armazenamento de ficheiros e objetos
- 0033Autorização
- 0035Runtime e toolchain
- 0036Camada de API
Contexto
Seção intitulada “Contexto”Cada app precisa de saber quem a está a chamar. A identidade atravessa todos os pontos de entrada que temos: SSR e server functions no TanStack Start (ADR-0002) e as server routes da API em /api/* (ADR-0036) para clientes externos. O rate limiting (ADR-0010) aplica os limites por utilizador e por token com base nesta identidade, e é por isso que a autenticação se decide primeiro.
Requisitos:
- Corre dentro do nosso contentor (ADR-0003) — sem serviço adicional para operar, a menos que se pague a si próprio
- Dados de utilizadores e sessões no nosso Postgres (ADR-0007), em tabelas nossas, migradas pelo Drizzle Kit como tudo o resto (ADR-0008)
- Sessões por cookie para a nossa UI, tokens para clientes de API — browsers e consumidores que não são browsers têm ambos um caminho de primeira classe
- Integração de primeira classe com TanStack Start — não uma biblioteca de Next.js que adaptamos
- Caminho de crescimento sem reescrita: fornecedores OAuth, 2FA, passkeys, organizações, SSO à medida que os produtos precisarem
- Sem preço por utilizador — o custo da autenticação não deve crescer com o nosso sucesso
Opções consideradas
Seção intitulada “Opções consideradas”1. Better Auth
Seção intitulada “1. Better Auth”Biblioteca de autenticação em TypeScript, agnóstica de framework. Corre em processo, guarda tudo na nossa base de dados através de um adapter e estende-se com plugins.
Prós
- Biblioteca autoalojada: corre no nosso contentor Bun (ADR-0035), sem serviço externo, sem custos por utilizador
- O adapter Drizzle (
@better-auth/drizzle-adapter,provider: "pg") declara suporte tanto para o Drizzle 0.45 como para o RC da v1 (ADR-0008); a sua CLI gera as tabelas de autenticação como esquema Drizzle, pelo que as migrações de autenticação passam pelo Drizzle Kit juntamente com as restantes - Integração oficial com TanStack Start (plugin
tanstackStartCookies()) e com Hono (auth.handler(c.req.raw)) - Os plugins cobrem o caminho de crescimento: 2FA, passkeys, magic links, organizações, SSO, bearer tokens e chaves de API para clientes que não são browsers
- Rate limiter integrado nos endpoints de autenticação, ativo por predefinição em produção
- Licença MIT; a equipa mantém também o Auth.js e recomenda o Better Auth para projetos novos
Contras
- Projeto jovem com cadência de lançamentos rápida; já publicou avisos de segurança (aplicamos os patches quando saem, não de acordo com um calendário)
- Adquirido pela Vercel (2026-07-07). Continua MIT e mantém o seu modelo de contribuição, mas o roadmap passa a estar dentro da empresa de plataforma que recusámos como destino de deploy (ADR-0003)
- A documentação de joins do adapter só mostra
relations()do Drizzle 0.x, não odefineRelations()da v1 — os joins no RC da v1 não estão verificados - Ficamos com a superfície de segurança da autenticação: configuração, segredos, patches
2. Auth.js (NextAuth.js)
Seção intitulada “2. Auth.js (NextAuth.js)”A opção open-source de longa data, pensada primeiro para Next.js.
Prós
- Enorme base instalada, muitas integrações com fornecedores
Contras
- Agora mantido pela equipa do Better Auth apenas com patches de segurança e correções urgentes; recomendam o Better Auth para projetos novos
- Moldado para Next.js; TanStack Start e Hono ficam em segundo plano
- Oferta integrada fraca para 2FA, passkeys, organizações
3. Autenticação gerida (Clerk, WorkOS AuthKit, Auth0)
Seção intitulada “3. Autenticação gerida (Clerk, WorkOS AuthKit, Auth0)”Identidade alojada por terceiros, com componentes de UI prontos a usar.
Prós
- Zero operação de autenticação; UI alojada, MFA, SSO empresarial e documentação de conformidade incluídos
- Aplicar patches de segurança é trabalho do fornecedor
Contras
- Preço por utilizador (MAU) que cresce com o sucesso
- Os dados dos utilizadores ficam fora do nosso Postgres — joins, exportações e migrações passam por uma API do fornecedor
- Lock-in e uma dependência de rede externa em cada início de sessão, contra o controlo que escolhemos no ADR-0003
4. Fornecedor de identidade autoalojado (Keycloak, Zitadel)
Seção intitulada “4. Fornecedor de identidade autoalojado (Keycloak, Zitadel)”Um servidor OIDC autónomo ao qual as nossas apps delegam.
Prós
- Baseado em standards (OIDC/SAML); um único login em vários produtos
- UI de administração madura, federação empresarial
Contras
- Um segundo serviço por ambiente para operar, atualizar, fazer backup e proteger
- Pesado para uma única app; redirecionamentos OIDC para todos os produtos, mesmo os pequenos
- Dados dos utilizadores repartidos entre a base de dados do IdP e a nossa
5. Fazer nós próprios (estilo Lucia, com Oslo + Arctic)
Seção intitulada “5. Fazer nós próprios (estilo Lucia, com Oslo + Arctic)”Sessões, hashing de palavras-passe e OAuth construídos a partir de pequenas primitivas. O Lucia é agora um recurso de aprendizagem, não uma biblioteca.
Prós
- Controlo total, dependências mínimas, nada escondido
Contras
- Escrevemos e revemos todas as linhas críticas para a segurança: gestão de sessões, CSRF, fluxos OAuth, reposição de palavras-passe, 2FA
- A funcionalidade de crescimento de cada produto (passkeys, orgs, SSO) passa a ser um projeto nosso
Decisão
Seção intitulada “Decisão”O Better Auth é a biblioteca de autenticação padrão da empresa para todas as novas apps.
Em concreto:
- Versão: a última estável, 1.7.x (1.7.11 à data de escrita). As novas apps começam na tag
latestdo npm — nuncabetanemrc(a 1.8 está em beta). Quatro pacotes avançam juntos na mesma versão:better-auth,@better-auth/drizzle-adapter,@better-auth/redis-storagee a CLI, que agora é o pacoteauth(o@better-auth/cliestá desatualizado na 1.4 — não usar) - Montado por uma server route do Start em
/api/auth/*:routes/api/auth/$.tsentregaGETePOSTaauth.handler(request), a configuração documentada do Better Auth para TanStack Start (ADR-0036). (Alterado em 2026-11-13: este ADR montou-o inicialmente no Hono; o ADR-0036 substituiu o Hono pelas server routes do Start.) - Base de dados: adapter Drizzle com
provider: "pg". As tabelas de autenticação são geradas no nosso esquema Drizzle pela CLI (authcomo devDependency na mesma versão dobetter-auth, executado comobunx auth generate, nãoauth@latest) e migradas pelo Drizzle Kit — uma única pipeline de migrações. Deixaradvanced.database.joinsdesligado até estar verificado com odefineRelations()do Drizzle v1 - Sessões de UI: cookies de sessão httpOnly. O
tanstackStartCookies()é o último plugin do array; o SSR e as server functions leem a sessão comauth.api.getSession({ headers })em middleware do Start - As consultas de sessão vão ao Valkey, não ao Postgres. O
secondaryStoragedo Better Auth usa o pacote oficial@better-auth/redis-storagesobre o clienteioredispartilhado e a instância Valkey do ADR-0010.session.storeSessionInDatabase: trueescreve também todas as sessões no Postgres: o Valkey é o caminho rápido, o Postgres o registo durável. As consultas verificam primeiro o Valkey e recorrem ao Postgres em caso de falha, pelo que um reinício do Valkey custa latência, não um logout em massa. A revogação apaga de ambos - Não usar
cookieCache: salta a consulta por completo, mas uma sessão revogada continua válida até o cookie em cache expirar. O Valkey mantém a revogação imediata - Clientes de API: os consumidores que não são browsers usam os plugins bearer ou API-key do Better Auth, escolhidos por produto. As rotas da API leem apenas o cabeçalho
Authorization, nunca o cookie de sessão (ADR-0036) - Os plugins são opt-in por produto — a instalação predefinida é email/palavra-passe mais os fornecedores OAuth de que o produto precisar
- O hashing das palavras-passe usa
Bun.password(argon2id, a predefinição do Bun) através deemailAndPassword.password.hasheverify, em vez do scrypt predefinido do Better Auth (ADR-0035). A OWASP lista o argon2id em primeiro lugar; o scrypt é a alternativa. (Alterado em 2026-11-13.) - Os avisos de segurança são corrigidos quando saem, não agrupados com as atualizações de rotina
Os fatores decisivos: corre no nosso contentor e mantém todas as linhas de utilizadores e sessões no nosso próprio Postgres, liga-se ao Drizzle Kit e ao TanStack Start sem código de cola, e os seus plugins cobrem o caminho de crescimento sem mudar de biblioteca. Aceitamos ficar com a superfície de segurança e o risco da tutela da Vercel — a licença MIT e as nossas próprias tabelas mantêm a saída em aberto.
Consequências
Seção intitulada “Consequências”Mais fácil:
- Uma única identidade em SSR, server functions e
/api/*— a chave de que o rate limiting precisa (ADR-0010) - Os dados dos utilizadores são tabelas Postgres normais: joins, relatórios, exportações e backups não precisam de API de fornecedor
- Acrescentar 2FA, passkeys ou organizações a um produto é um plugin e uma migração, não uma migração para uma nova biblioteca
- A verificação de sessão em cada pedido é uma leitura no Valkey, não uma query ao Postgres
Mais difícil:
- Ficamos com a segurança da autenticação: rotação de segredos, definições de cookies e aplicação rápida de patches a cada aviso
- O limiter integrado do Better Auth está desligado (
rateLimit: { enabled: false }); o limiter do ADR-0010 cobre/api/auth/*, e a sua lista de endpoints de credenciais tem de acompanhar as atualizações do Better Auth - As atualizações tocam no esquema de autenticação; atualizar os quatro pacotes do Better Auth em conjunto, regenerar o esquema e rever a migração do Drizzle a cada atualização do Better Auth
- O Valkey passa a guardar tokens de sessão ativos. O seu isolamento por palavra-passe e por rede (ADR-0010) são controlos de segurança: um Valkey acessível significa sessões sequestráveis
- Com
secondaryStoragedefinido, os registos de verificação (verificação de email, magic links, OTPs, reposição de palavras-passe) e o estado do OAuth vivem apenas no Valkey (verification.storeInDatabaseéfalsepor predefinição). Um reinício do Valkey anula ligações pendentes e inícios de sessão OAuth em curso; os utilizadores pedem-nos de novo. Aceite — são de curta duração por definição - O
@better-auth/redis-storageexigeioredis5.x; manter na 5.x até o seu intervalo de peers permitir a 6 - Duas dependências em rápida evolução cruzam-se aqui (a cadência de lançamentos do Better Auth, o RC do Drizzle v1); testar os fluxos de início de sessão a cada atualização de qualquer uma
Do que abdicamos: da segurança zero-ops e da UI alojada da autenticação gerida, e do single sign-on entre produtos de um IdP central. Aceite — o controlo dos nossos dados de utilizadores e a ausência de preço por utilizador importam-nos mais à nossa escala; um produto que precise de SSO empresarial pode adicionar o plugin de SSO do Better Auth ou escrever um ADR de exceção.

