Communitygithub.com

rbikbov/frontend-starter

Autorização e sessão no frontend com Next.js (App Router): onde armazenar tokens, BFF com route handlers usando HttpOnly-cookie, renovação de token sem corridas, detecção de 'não autorizado' (incluindo GraphQL que responde 200), verificações em proxy.ts e Data Access Layer, redirecionamento seguro após login, logout sem vazamento de dados do usuário anterior, tokens em links de redefinição de senha, formulários de login e recuperação. Use ao projetar ou implementar login, registro, recuperação de senha, páginas protegidas, cliente de API com refresh token, ou ao revisar tal código.

O que é frontend-starter?

Este é um guia abrangente e prático para implementar autenticação e gerenciamento de sessão no frontend com Next.js (App Router). Voltado para desenvolvedores que estão construindo ou revisando sistemas de login, registro, recuperação de senha e páginas protegidas, o documento aborda desde a escolha do modelo de sessão (BFF com HttpOnly-cookie, bibliotecas como Auth.js, ou tokens em memória) até práticas avançadas de segurança. Ele define regras claras (MUST/SHOULD) para configuração de cookies, implementação de route handlers no BFF, verificação de sessão em diferentes camadas (proxy.ts, Data Access Layer), e renovação de tokens sem condições de corrida (single-flight). O guia também cobre redirecionamento seguro após login, logout sem vazamento de dados, tratamento de tokens em URLs (como redefinição de senha) e formulários de autenticação. Suporta Next.js 16+ (com proxy.ts) e requisitos específicos de GraphQL. O objetivo é oferecer uma abordagem robusta e testável para sessão e autorização, priorizando segurança contra XSS, CSRF e vazamento de dados entre usuários na mesma aba.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/rbikbov/frontend-starter/tree/HEAD/.claude/skills/frontend-auth-session

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Autorização e Sessão no Next.js

Documento sobre o frontend que recebe a sessão do backend (tokens ou id de sessão) e deve armazená-la, renová-la e verificá-la. Se o projeto usa uma biblioteca ou provedor (Auth.js, Better Auth, Clerk etc.) — a mecânica da sessão fica por conta dela, mas as regras das seções 2, 6–10 permanecem em vigor. Código — references/implementation.md.

Prioridade das regras. Instruções do usuário e arquivos de agente do projeto são mais importantes. Se o backend ditar algo diferente (por exemplo, ele mesmo definir o cookie) — siga o backend e escreva a diferença no README ou ADR.

Notações: MUST — não pode ser violado; SHOULD — sim por padrão, desvios exigem justificativa.


1. Escolha do Modelo

ModeloQuando usar
BFF: route handlers do Next recebem tokens do backend e os colocam em HttpOnly-cookieBackend retorna tokens no corpo da resposta. Opção padrão para Next
Backend define o HttpOnly-cookie por conta própriaFrontend e API no mesmo site, backend sob seu controle
Biblioteca ou provedorPrecisa de OAuth, MFA, gerenciamento de usuários — não implemente isso manualmente
Access na memória, refresh em HttpOnly-cookieSPA sem BFF
Tokens em localStorage/sessionStorageProibido: qualquer XSS leva a sessão embora

Segunda bifurcação — o que o BFF faz de proxy:

OpçãoPrósContras
Apenas login / refresh / logout, as demais requisições vão direto para a API com o cookieBFF não é gargalo; menos códigoAPI lê o token do cookie e configura CORS; cookies precisam chegar ao domínio da API
Todas as requisições passam pelo BFFAPI não precisa saber sobre cookie e CORS; mesma origemCada requisição é um hop extra; BFF precisa ser escalado

A escolha é registrada em ADR.


2. Cookie (MUST)

  • HttpOnly para tudo que dá acesso (access, refresh, id de sessão).
  • Secure em produção. Localmente, navegadores permitem Secure em http://localhost, mas não em outros hosts http.
  • SameSite=Lax por padrão. Strict é mais seguro contra CSRF, mas o usuário que chega por um link de email ou de outro site, na primeira navegação parece não autenticado — escolha conscientemente e registre o motivo.
  • Path=/; tempo de vida — baseado no exp do token ou na resposta do backend, não "no olho".
  • SHOULD: prefixo __Host- no nome (exige Secure, Path=/, sem Domain) — o cookie não pode ser substituído de um subdomínio.
  • Cookies são definidos e removidos apenas no servidor (route handler, Server Action, proxy.ts). Server Components não podem definir cookies.
  • Cookie não legível por JS — o único portador da sessão. Uma flag is_authenticated sem HttpOnly é permitida apenas como dica para UI e nunca é usada para decisões de acesso.

3. BFF: route handlers (MUST)

  • O login é validado por schema (zod); corpo com formato inválido — 400.
  • Handlers mutantes verificam Origin contra a origin da aplicação: SameSite=Lax não protege contra requisições de subdomínios do mesmo site. Server Actions comparam Origin com Host por conta própria.
  • O contexto do cliente é passado para o backend: X-Forwarded-For, User-Agent — caso contrário, o rate limit do backend vê apenas o IP do servidor.
  • Erros do backend são traduzidos em resposta com kind e status (401, 403, 422, 429 com Retry-After, 503); o texto do backend não vaza.
  • Refresh: se o backend rejeitou o refresh token — remova o cookie e responda 401. Se o backend estiver indisponível (rede, 5xx) — não mexa no cookie, responda 503: uma falha temporária não deve fazer logout.
  • Logout: revogue o token no backend (registre o erro, mas não exiba), remova o cookie em qualquer caso.

4. Onde Verificar a Sessão (MUST)

LocalO que fazO que não faz
proxy.ts (Next 16+, antes middleware.ts)Verificação otimista pela presença do cookie: redireciona para login e de páginas de loginNão consulta BD ou API; não é a única proteção
Data Access Layer (verifySession() em módulo server-only)Verificação junto com dados: em Server Components, route handlers, Server Actions—
LayoutNada: layouts não são re-renderizados durante navegaçãoNão verifica acesso
BackendFonte da verdade: verifica o token em cada requisição—
  • Cada route handler e Server Action é um ponto de entrada público e chama verifySession() por conta própria.
  • proxy.ts verifica o refresh cookie (ou id de sessão): o access pode ter expirado, mas a sessão ainda estar viva.
  • Correspondência de caminho — por segmentos: /app e /app/…, mas não /apple.
  • Com cacheComponents (padrão em projetos Next 16.4), a leitura de sessão e cache de dados do usuário têm suas próprias regras: antes de escrever DAL, leia node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md.
  • Permissões (papel, propriedade) são decididas com base em dados verificados: pela assinatura (JWKS, jose) ou pela resposta do backend. Payload JWT não verificado não é base para acesso.
  • Server Components não podem atualizar cookies. Se o access expirou no momento da renderização no servidor: ou o proxy.ts atualiza a sessão (ele pode definir cookies na resposta — faça isso apenas para navegações de documento, não para prefetch, e considere a rotação de refresh tokens), ou a página renderiza carregamento no cliente e o refresh é feito pelo cliente.

5. Cliente: refresh sem corridas (MUST)

  • Definição de 'não autorizado': HTTP 401, ou em resposta GraphQL (status 200) pelo menos um erro com extensions.code === "UNAUTHENTICATED" (errors.some, não errors[0]). Comparação com texto da mensagem — apenas se o backend não retornar códigos, em uma única função, com comentário e teste.
  • Single-flight: requisições paralelas que recebem 401 esperam um único refresh compartilhado e usam seu resultado. A promise é resetada em finally.
  • A requisição original é repetida uma vez. Um segundo 401 — sessão perdida, sem segundo refresh.
  • A própria requisição de refresh não passa pelo interceptador (senão, recursão).
  • O resultado do refresh distingue três desfechos: emitido (true) → repetir; rejeitado (false) → sessão expirou; rede ou 5xx (exceção) → erro network, sem sair da conta.
  • Durante o logout, o refresh é bloqueado: caso contrário, uma requisição que recebeu 401 no momento do logout restauraria imediatamente a sessão.
  • Sessão expirada: limpe o cache de requisições, leve para a página de login com callbackUrl da página atual.
  • O corpo da requisição a ser repetida deve ser repetível (string, FormData), não um ReadableStream de uso único.

6. Redirecionamento após Login (MUST)

  • callbackUrl (next, returnTo) é verificado pela função getSafeRedirect(raw, fallback, appOrigin): rejeita \ e caracteres de controle (navegadores transformam /\evil.com e /\<tab>/evil.com em //evil.com), analisa new URL(raw, appOrigin), compara a origin e retorna apenas o caminho. Verificação 'começa com / e não com //' — insuficiente.
  • Conjunto de testes para ela — references/implementation.md, seção 7.
  • Após o login, Server Components devem ver os novos cookies: navegação completa (window.location.assign) ou router.replace + router.refresh().

7. Logout (MUST)

  • O servidor remove o cookie (seção 3).
  • O cliente limpa o cache de requisições (queryClient.clear()) e todos os stores do cliente com dados do usuário: o próximo usuário na mesma aba não deve ver dados de outro, nem que por um instante.
  • Navegação para a página de login sem possibilidade de voltar 'para trás' para uma tela protegida em cache (replace ou navegação completa).
  • Confirmação de logout — opcional, a critério do produto; bloquear o botão durante a requisição — obrigatório.
  • SHOULD: outras abas devem saber do logout (BroadcastChannel ou evento storage por flag), senão continuam exibindo dados até o primeiro 401.

8. Tokens em Links (redefinição de senha, confirmação de email)

  • O token é lido da URL uma vez em estado, em seguida o endereço é substituído sem o token (router.replace(pathname)): ele não fica no histórico nem vaza no Referer.
  • Para essas páginas — Referrer-Policy: no-referrer e nenhum script de terceiros (analytics, widgets).
  • O token não é registrado em logs. O caráter de uso único e o tempo de vida são garantidos pelo backend; o frontend exibe uma tela clara 'link expirou' com a ação 'solicitar novo'.

9. Formulários de Login e Recuperação

  • A recuperação de senha responde igualmente para email existente e inexistente ('se a conta existir, um email foi enviado'), sem revelar quem está registrado. Se o backend revelar — registre como limitação.
  • O envio é bloqueado durante a requisição; novo envio não cria uma segunda requisição.
  • 429: mensagem com tempo de espera do Retry-After, se presente.
  • Campos: type="email" + autoComplete="email"; senha — autoComplete="current-password" no login e "new-password" no registro e alteração; a inserção não é bloqueada.
  • Botão 'mostrar senha' — type="button", aria-label e aria-pressed, não rouba o foco do campo.
  • O erro 'email ou senha inválidos' não especifica qual deles está errado.
  • Erros do servidor são mapeados aos campos por código; erro geral — acima do formulário.

10. Dados e Logs

  • Respostas dependentes do usuário não entram em caches compartilhados ("use cache", CDN). QueryClient no servidor — novo a cada requisição.
  • Nos logs não há tokens, senhas, emails. No máximo — id do usuário e kind do erro.
  • Payload do token de sessão é mínimo: id e papel, sem dados pessoais.

11. Testes

  • Unitário: getSafeRedirect (conjunto completo de references); definição de 'não autorizado' (401, códigos GraphQL, corpo vazio, HTML em vez de JSON).
  • Single-flight: N requisições paralelas com 401 → um refresh, N repetições; refresh rejeitado → nenhuma repetição, uma chamada para onSessionExpired; refresh falhou por rede → erro network, sem logout; segunda 401 após refresh bem-sucedido → sem segundo refresh; durante logout → sem refresh.
  • Route handlers: flags de cookie; refresh rejeitado → cookie removido; backend indisponível → cookie intacto, 503; corpo inválido → 400; Origin estranho → 403.
  • e2e: login → página protegida → logout → página protegida inacessível; expiração do access (TTL curto no ambiente de teste) → refresh transparente; link de redefinição de senha → token desapareceu da barra de endereço.

12. Definition of Done

  • Nenhum token é lido do JS; localStorage não é usado para sessão.
  • Cookie: HttpOnly, Secure em produção, SameSite, Path, vida baseada no token.
  • Refresh: rejeição e indisponibilidade do backend são diferenciadas.
  • Single-flight com resultado, uma repetição, bloqueio durante logout — coberto por testes.
  • proxy.ts — apenas verificação otimista; DAL é chamada em handlers, actions e Server Components.
  • getSafeRedirect com conjunto completo de testes.
  • Logout limpa cache e stores do cliente.
  • Tokens em links são removidos da barra de endereço.
  • Nos logs não há tokens nem dados pessoais.
  • Escolha do modelo e proxy está registrada em ADR.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

rbikbov/frontend-starter

Creates Architecture Decision Records documenting key technical decisions with context, alternatives considered, tradeoffs, consequences, and decision owners. Use when documenting "architecture decisions", "technical choices", "design decisions", or "ADRs".

rbikbov/frontend-starter

Проверка фронтенд-кода и документации на «нейрослоп» перед коммитом, PR или публикацией репозитория: остатки генераторов, мёртвые и недоделанные опции, утверждения в README и комментариях, не подтверждённые кодом, маркетинговый тон и эмодзи, лишние абстракции, хрупкая обработка ошибок, персональные данные в логах, несуществующие классы стилей. Использовать, когда просят проверить код или репозиторий «на слоп», подготовить проект к публикации или показу, сделать ревью кода, написанного с помощью ИИ, или перед тем как закоммитить собственную большую правку.

rbikbov/frontend-starter

Процесс разработки фичи с агентом: PRD с Non-Goals → компактная спецификация → задачи по одной на коммит → read-only исследование → tracer bullet → сборка с тестом сначала → независимое ревью субагентом → проверка в браузере. Использовать, когда просят сделать фичу, экран, пользовательский сценарий или заметное изменение поведения; когда нужно написать PRD, спецификацию или разбить работу на задачи; когда работа больше одного коммита. Не использовать для опечаток и однострочных правок.

rbikbov/frontend-starter

Старт нового фронтенд-проекта на Next.js (App Router) + TypeScript: фундамент, который закладывается до первой фичи. Использовать, когда нужно: создать проект с нуля; решить, нужны ли Next.js, монорепо, BFF, микрофронтенды; разложить FSD поверх роутинга Next; завести строгий TS, линтер границ, env-конфиг, модель ошибок и API-клиент, тесты, git-хуки, CI, каркас README; навести порядок в молодом проекте после генератора.

Habilidades Relacionadas