Pular para o conteúdo

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

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 o defineRelations() 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

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

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 latest do npm — nunca beta nem rc (a 1.8 está em beta). Quatro pacotes avançam juntos na mesma versão: better-auth, @better-auth/drizzle-adapter, @better-auth/redis-storage e a CLI, que agora é o pacote auth (o @better-auth/cli está desatualizado na 1.4 — não usar)
  • Montado por uma server route do Start em /api/auth/*: routes/api/auth/$.ts entrega GET e POST a auth.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 (auth como devDependency na mesma versão do better-auth, executado como bunx auth generate, não auth@latest) e migradas pelo Drizzle Kit — uma única pipeline de migrações. Deixar advanced.database.joins desligado até estar verificado com o defineRelations() 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 com auth.api.getSession({ headers }) em middleware do Start
  • As consultas de sessão vão ao Valkey, não ao Postgres. O secondaryStorage do Better Auth usa o pacote oficial @better-auth/redis-storage sobre o cliente ioredis partilhado e a instância Valkey do ADR-0010. session.storeSessionInDatabase: true escreve 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 de emailAndPassword.password.hash e verify, 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.

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 secondaryStorage definido, 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 é false por 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-storage exige ioredis 5.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.