Alternar tema

JWT ou Session? Um guia prático para escolher sem complicação

Easton editorial illustration: rendering-mode selector

O cursor fica piscando entre session: { strategy: "jwt" } e session: { strategy: "database" }. O projeto está prestes a entrar no ar, mas você ainda não sabe se deve usar JWT ou Session.

Na hora de escolher uma tecnologia, as duas alternativas parecem boas, mas nem sempre fica claro qual combina melhor com o projeto. JWT não mantém estado nem consulta o banco de dados, porém não permite revogação imediata. Session mantém estado e oferece controle instantâneo, mas consulta o banco a cada requisição. Neste artigo, vamos comparar as duas estratégias de sessão e entender quando escolher cada uma.

Primeiro, entenda o que cada uma é

Quando o assunto é JWT e Session, muita gente sabe repetir as definições, mas explicar a diferença de forma clara é outra história. Gosto de usar esta analogia:

Session é como uma carteirinha de academia. Ao se matricular, a academia registra seus dados no sistema. Sempre que você chega para treinar, basta passar a carteirinha — ou fornecer um Session ID — para que a recepção consulte quem você é, quando o plano vence e quantas aulas ainda restam. Todas as informações importantes ficam no banco de dados da academia.

JWT é como um documento de identidade. Todas as suas informações, como nome, data de nascimento e endereço, estão impressas no documento. Quando você precisa provar quem é, basta apresentá-lo: a outra pessoa consegue identificar você sem consultar outro sistema.

Com isso, o funcionamento de cada estratégia fica mais claro:

  • Com Session: depois do login, o servidor gera um Session ID, armazena os dados do usuário no banco e envia esse identificador ao navegador por meio de um Cookie. Em cada requisição posterior, o navegador envia o Session ID, e o servidor consulta os dados correspondentes no banco.

  • Com JWT: depois do login, o servidor reúne os dados do usuário em um token protegido, o JWT, e o envia diretamente ao navegador. Nas requisições seguintes, o navegador envia o JWT, e o servidor só precisa validar o token, sem consultar o banco de dados.

0
Consultas ao banco com JWT
Requisições posteriores dispensam consultas
Todas
Consultas ao banco com Session
Cada requisição consulta o banco
15 minutos
Expiração recomendada para JWT
Com um refresh token
4 KB
Limite de tamanho do Cookie
O token JWT não pode ser grande demais
Source: Resumo de experiências práticas

Análise detalhada de JWT

JWT se tornou bastante popular entre desenvolvedores, especialmente em equipes que trabalham com Serverless e microsserviços. O motivo é simples: você não precisa se preocupar com o banco de dados a cada requisição.

Um amigo criou um site de comércio eletrônico implantado na Vercel e escolheu JWT. Segundo ele, a maior vantagem foi a praticidade: não era preciso se preocupar com o limite de conexões do banco, com o desempenho de uma tabela de Sessions nem com a implantação em nós de edge. O usuário faz login uma vez, recebe o JWT e passa a enviá-lo em todas as requisições; o servidor valida a assinatura e pronto.

JWT é especialmente indicado nestes cenários:

1. Serverless e edge computing
Se o projeto está implantado na Vercel, no Cloudflare Workers ou em plataformas semelhantes, JWT é quase uma escolha natural. As funções dessas plataformas não mantêm estado, e cada requisição pode ser processada em um servidor diferente. Com Session, você ainda precisaria de algo como Redis para compartilhar os dados entre instâncias. Com JWT é mais simples: o token contém as informações necessárias, independentemente do servidor que processar a requisição.

2. Cenários de alta concorrência
Quem já trabalhou em campanhas com picos de acesso sabe que consultas ao banco podem se tornar um gargalo. Com Session, cada requisição consulta o banco; com JWT, a consulta ocorre no login e as requisições posteriores usam validação local, o que é muito mais rápido.

3. Arquitetura de microsserviços
Quando existem vários serviços, como usuários, pedidos e pagamentos, JWT permite compartilhar as informações de autenticação entre eles. Cada serviço não precisa consultar a Session do usuário nem depender de um armazenamento centralizado de Sessions.

As armadilhas de JWT

Parece ótimo, mas JWT também traz problemas, e alguns deles são importantes.

A principal armadilha: não é possível revogar imediatamente

Certa vez, descobrimos que a conta de um usuário tinha sido invadida e queríamos encerrar seu acesso na mesma hora. Com Session, bastaria apagar o registro do banco. Como estávamos usando JWT, surgiu o problema: depois de emitido, o token continua válido até expirar e não pode ser revogado diretamente.

Talvez você pense em criar uma lista de bloqueio. Isso funciona, mas elimina a vantagem de não manter estado: se cada requisição precisa consultar a lista, qual é a diferença prática em relação a consultar uma tabela de Sessions?

Por isso, ao usar JWT, normalmente se define uma expiração curta, como 15 minutos, acompanhada de um refresh token. Assim, mesmo que o token seja roubado, ele deixará de funcionar em no máximo 15 minutos.

Limite de tamanho do Cookie

JWT codifica os dados do usuário no próprio token. Se você incluir informações demais, como funções, permissões e preferências, o token ficará grande. Como Cookies de navegador têm um limite de 4 KB, talvez ele nem possa ser armazenado.

Fica a lição: não coloque tudo no JWT. Armazene somente o essencial, como o ID do usuário e a data de expiração. Consulte as demais informações apenas quando forem necessárias.

Configuração prática de JWT no NextAuth.js

Se você usa Next.js, o NextAuth.js, hoje chamado Auth.js, é uma das soluções de autenticação mais populares. Configurar JWT é simples:

// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitHub],
  session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60, // 30 dias
  },
  callbacks: {
    async jwt({ token, user }) {
      // No primeiro login, adiciona os dados do usuário ao token
      if (user) {
        token.id = user.id
        token.role = user.role
      }
      return token
    },
    async session({ session, token }) {
      // Disponibiliza ao cliente os dados armazenados no token
      if (token) {
        session.user.id = token.id
        session.user.role = token.role
      }
      return session
    },
  },
})

Alguns pontos importantes:

  1. Defina NEXTAUTH_SECRET obrigatoriamente — no Auth.js v5, o nome mudou para AUTH_SECRET. Você pode executar npx auth secret para gerar uma chave segura. Ela é usada para assinar e criptografar o JWT e jamais deve ser exposta.

  2. Sessão deslizante: se quiser renovar a sessão automaticamente enquanto o usuário estiver ativo, configure updateAge:

session: {
  strategy: "jwt",
  maxAge: 30 * 24 * 60 * 60,
  updateAge: 24 * 60 * 60, // Atualiza uma vez a cada 24 horas
}

Assim, se houver alguma atividade dentro de 24 horas, a Session será renovada automaticamente e o usuário não será desconectado de repente.

  1. Mudança de 2025: para implantar no Edge Runtime, como em middleware, é necessário separar a configuração:
// auth.config.ts - pode ser executado no Edge
export default {
  providers: [GitHub],
  session: { strategy: "jwt" },
}

// auth.ts - contém operações de banco e roda somente no Node.js
import { PrismaAdapter } from "@auth/prisma-adapter"
import authConfig from "./auth.config"

export const { handlers, auth } = NextAuth({
  ...authConfig,
  adapter: PrismaAdapter(prisma),
})

Isso acontece porque o Edge Runtime não oferece suporte a algumas APIs do Node.js. Com a separação, o middleware usa auth.config.ts, enquanto as rotas do servidor usam o auth.ts completo.

Análise detalhada de Database Session

Quando Session é indispensável?

Apesar da praticidade de JWT, há situações em que Session é a escolha certa.

Em um sistema administrativo que desenvolvi para uma empresa financeira, o requisito era explícito: ao detectar um login suspeito, deveria ser possível desconectar o usuário imediatamente. Nesse caso, JWT não era adequado.

Database Session funciona melhor nestes cenários:

1. Aplicações que exigem controle imediato
Alguns exemplos são limitar o login a um único dispositivo, forçar o logout por decisão de um administrador ou invalidar todos os acessos assim que a senha for alterada. Com Session, esses requisitos são simples de implementar; com JWT, exigem bastante lógica adicional.

2. Contextos com alta exigência de segurança
Setores como bancos, saúde e governo precisam validar o estado mais recente das permissões em todas as requisições. Session permite consultar o banco a cada vez e fazer com que qualquer alteração de permissão entre em vigor imediatamente.

3. Aplicações monolíticas tradicionais
Se a aplicação usa uma arquitetura monolítica tradicional, com servidor e banco próximos, Session não representa um grande peso. A latência da consulta é baixa, e o controle sobre a sessão é maior.

O problema de desempenho de Session

O maior problema é que cada requisição precisa consultar o banco de dados. Em uma aplicação com muito tráfego, isso pode virar um gargalo.

Há algumas maneiras de otimizar:

  1. Armazene Sessions no Redis: ele é muito mais rápido que um banco tradicional e oferece expiração automática.

  2. Reduza os dados da Session ao mínimo: guarde apenas o ID do usuário e consulte o restante quando necessário.

  3. Defina uma expiração adequada: remova as Sessions vencidas a tempo para impedir que a tabela cresça sem controle.

Configuração prática de Session no Next.js

Por padrão, o NextAuth.js usa JWT. Para trabalhar com Database Session, você precisa configurar um adapter:

// auth.ts
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import GitHub from "next-auth/providers/github"

const prisma = new PrismaClient()

export const { handlers, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [GitHub],
  session: {
    strategy: "database",
    maxAge: 30 * 24 * 60 * 60, // 30 dias
    updateAge: 24 * 60 * 60, // Atualiza diariamente
  },
})

O Prisma precisa de uma tabela Session. Ao executar npx prisma db push, ela será criada automaticamente:

// schema.prisma
model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id       String    @id @default(cuid())
  email    String    @unique
  sessions Session[]
}

Dicas de otimização de desempenho:

  1. Crie índices para os campos sessionToken e expires; isso acelera bastante as consultas.

  2. Remova Sessions expiradas periodicamente com uma tarefa agendada:

// cleanup-sessions.ts
async function cleanupExpiredSessions() {
  await prisma.session.deleteMany({
    where: {
      expires: {
        lt: new Date(),
      },
    },
  })
}

// Executa todos os dias às 3h

Estrutura prática para tomar a decisão

Depois de tanta teoria, como escolher? Resumi os critérios em uma estrutura que você pode aplicar ao seu projeto.

Escolha de acordo com o tipo de projeto

Tipo de projetoRecomendaçãoMotivo
Aplicação ServerlessJWTNão mantém estado nem exige armazenamento compartilhado de Sessions
Aplicação monolítica tradicionalSessionO banco está próximo, então a consulta tem baixo custo
Arquitetura de microsserviçosJWT ou híbridaFacilita a autenticação entre serviços
Site de conteúdo ou blogJWTAs necessidades de autenticação são simples e JWT é suficiente
Painel de SaaSSessionExige controle detalhado de permissões

Escolha de acordo com os requisitos de segurança

  • Baixa sensibilidade — blogs, fóruns e sites de conteúdo: JWT com expiração de 30 dias
  • Sensibilidade média — comércio eletrônico e redes sociais: JWT com expiração curta e refresh token
  • Alta sensibilidade — finanças, saúde e painéis administrativos: Session com expiração curta e sessão deslizante

Análise de casos reais

Caso 1: site de comércio eletrônico — escolha por JWT

O site de comércio eletrônico do meu amigo acabou usando JWT pelos seguintes motivos:

  • Foi implantado na Vercel com arquitetura Serverless, que favorece JWT
  • Tinha muitos usuários, e reduzir consultas ao banco ajudava a diminuir custos
  • Não exigia controle rigoroso de sessão; era aceitável que o token levasse algum tempo para perder a validade após o logout

A única concessão foi definir a validade do JWT em sete dias, e não nos 30 dias mais comuns. Assim, mesmo que uma conta seja invadida, o token deixa de funcionar automaticamente após sete dias.

Caso 2: sistema de gestão empresarial — escolha por Session

Outro projeto era um sistema interno de gestão empresarial, que escolheu Session:

  • Havia controle rigoroso de permissões, e alterações de função precisavam entrar em vigor imediatamente
  • Era necessário limitar a quantidade de dispositivos conectados ao mesmo tempo
  • O sistema estava implantado na rede interna, então a latência da consulta ao banco era irrelevante

Nesse cenário, Session era claramente mais adequada. Também usamos Redis para armazenar as Sessions e mantivemos o tempo de resposta abaixo de 50 ms, o que era mais do que suficiente.

Caso 3: solução híbrida — a abordagem da Clerk

Hoje, muitas plataformas de autenticação, como a Clerk, usam uma solução híbrida:

  • Token de curto prazo, em JWT: válido por 15 minutos e usado nas chamadas de API
  • Token de longo prazo, o Refresh Token: armazenado no banco e usado para renovar o token de curto prazo
  • Registro de Session: armazena as Sessions ativas no banco e permite revogação remota

Isso combina o desempenho de JWT com o controle de Session. A implementação, porém, é um pouco mais complexa e faz sentido em projetos com requisitos elevados tanto de segurança quanto de experiência do usuário.

Minha recomendação

Se você ainda estiver em dúvida, sugiro o seguinte:

  1. Use JWT por padrão: ele atende à maioria dos cenários, tem configuração simples e bom desempenho.

  2. Troque para Session nestes casos:

    • É preciso limitar logins em vários dispositivos
    • É preciso desconectar um usuário imediatamente
    • O projeto envolve finanças, saúde ou outro contexto de alta segurança
    • Todos os acessos precisam perder a validade assim que a senha for alterada
  3. Evite complexidade desnecessária: se o projeto não for grande, comece com a alternativa simples e otimize quando surgir um gargalo real. Já vi muitos projetos adotarem uma solução híbrida desde o início e aumentarem drasticamente a complexidade do código sem jamais precisarem desses recursos.

Boas práticas para tratar a expiração da sessão

Depois de escolher a estratégia, ainda é preciso tratar bem a expiração. Se isso for mal implementado, a experiência do usuário pode ser péssima.

Tratamento da expiração de JWT

O pior cenário acontece quando o usuário está preenchendo um formulário e, ao enviar, recebe a mensagem de que o login expirou e perde tudo o que digitou.

Solução: Refresh Token e renovação silenciosa

A ideia funciona assim:

  1. Envie dois tokens ao usuário:

    • Access Token: válido por 15 minutos e usado nas chamadas de API
    • Refresh Token: válido por 30 dias e usado para obter um novo Access Token
  2. Quando o Access Token estiver perto de expirar, por exemplo, faltando dois minutos, o frontend usa automaticamente o Refresh Token para obter outro Access Token

  3. O usuário não percebe o processo e não é desconectado de repente

O NextAuth.js oferece esse mecanismo. Basta configurar:

callbacks: {
  async jwt({ token, user, account }) {
    if (account && user) {
      // Primeiro login
      return {
        ...token,
        accessToken: account.access_token,
        accessTokenExpires: Date.now() + account.expires_in * 1000,
        refreshToken: account.refresh_token,
      }
    }

    // O Access Token ainda é válido
    if (Date.now() < token.accessTokenExpires) {
      return token
    }

    // O Access Token expirou; usa o Refresh Token para renová-lo
    return refreshAccessToken(token)
  },
}

async function refreshAccessToken(token) {
  try {
    const response = await fetch("https://api.example.com/oauth/token", {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        client_id: process.env.OAUTH_CLIENT_ID,
        grant_type: "refresh_token",
        refresh_token: token.refreshToken,
      }),
    })

    const refreshedTokens = await response.json()

    return {
      ...token,
      accessToken: refreshedTokens.access_token,
      accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
      refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
    }
  } catch (error) {
    return {
      ...token,
      error: "RefreshAccessTokenError",
    }
  }
}

Tratamento da expiração de Session

Tratar a expiração de Session é relativamente simples, mas há alguns detalhes importantes.

Sessão deslizante versus tempo limite absoluto

  • Sessão deslizante: prolonga a sessão enquanto o usuário estiver ativo e serve para a maioria dos cenários
  • Tempo limite absoluto: desconecta o usuário ao chegar ao prazo, independentemente de sua atividade, e é mais indicado para contextos de alta segurança, como bancos

A configuração de sessão deslizante no NextAuth.js é simples:

session: {
  strategy: "database",
  maxAge: 2 * 60 * 60, // Tempo limite absoluto de 2 horas
  updateAge: 30 * 60, // Atualiza se houver atividade dentro de 30 minutos
}

Com essa configuração, se o usuário realizar alguma ação dentro de 30 minutos, a sessão será prolongada. Ainda assim, depois de duas horas ele será desconectado obrigatoriamente.

Tempo limite por inatividade

Em alguns casos, você pode querer desconectar o usuário automaticamente depois de 15 minutos sem atividade. É possível adicionar um temporizador no frontend:

let idleTimer
function resetIdleTimer() {
  clearTimeout(idleTimer)
  idleTimer = setTimeout(() => {
    // Desconecta após 15 minutos sem atividade
    signOut()
  }, 15 * 60 * 1000)
}

// Monitora a atividade do usuário
document.addEventListener("mousemove", resetIdleTimer)
document.addEventListener("keypress", resetIdleTimer)

Novas tendências de 2025

Para terminar, vale observar algumas tendências de 2025 que podem influenciar sua escolha.

Soluções híbridas se tornam comuns

Cada vez mais equipes percebem que JWT e Session não são alternativas mutuamente exclusivas. Por exemplo:

  • JWT para autenticação de API, com mais velocidade
  • Banco de dados para registrar Sessions ativas e manter o controle
  • Combinação das vantagens das duas abordagens

Plataformas de autenticação como Clerk e Auth0 adotam esse modelo. Se você não quiser implementá-lo por conta própria, também pode usar um desses serviços.

O impacto do Edge Runtime

O middleware do Next.js roda hoje no Edge Runtime, que é mais favorável a JWT. Se a lógica de autenticação precisar ficar no middleware, como ao proteger todo o caminho /dashboard, JWT será mais conveniente.

Passkeys e WebAuthn

Embora não sejam o tema principal, Passkeys baseadas em WebAuthn estão ganhando espaço. Muitos sites já permitem login com impressão digital ou Face ID, sem senha. O fluxo de autenticação fica mais complexo, mas a experiência do usuário melhora. O NextAuth.js v5 já oferece suporte a Passkeys.

Considerações finais

Agora você já deve ter uma visão mais clara sobre JWT e Session. Não existe uma resposta única: o essencial é entender as vantagens e limitações de cada estratégia e escolher de acordo com as necessidades reais do projeto.

Minha experiência é que JWT basta para a maioria dos projetos; quando houver necessidade de controle imediato, considere Session. Não complique desde o início: soluções simples costumam ser mais confiáveis.

Se ainda estiver em dúvida, comece com a configuração padrão do NextAuth.js, que usa JWT, e coloque o projeto para funcionar. Caso apareça uma necessidade real, migrar para Session não é difícil e exige poucas mudanças de configuração.

Você ainda está acordado às três da manhã pensando nessa decisão técnica? Se estiver, talvez seja melhor dormir e decidir no dia seguinte. Muitas vezes, depois de uma boa noite de sono, o problema deixa de parecer tão complicado.

Se tiver alguma opinião sobre o artigo ou estiver lidando com um cenário que não mencionei, compartilhe nos comentários. Tecnologia fica mais interessante quando a discussão é coletiva.

FAQ

Qual é a principal diferença entre JWT e Session?
JWT não mantém estado no servidor: as informações do usuário ficam codificadas no token, e o servidor só precisa validar a assinatura, sem consultar o banco de dados. Session mantém estado: os dados ficam no banco, que é consultado a cada requisição. JWT funciona bem em Serverless e alta concorrência; Session é melhor quando você precisa de controle imediato.
Quando devo usar JWT e quando devo usar Session?
Use JWT em Serverless, edge computing, alta concorrência, microsserviços, sites de conteúdo e blogs. Use Session quando precisar de controle imediato, como limitar logins em vários dispositivos ou forçar o logout, em contextos de alta segurança, como finanças e saúde, ou em aplicações monolíticas tradicionais. A recomendação padrão é começar com JWT e migrar para Session quando houver necessidade real de controle imediato.
O que fazer se um JWT não puder ser revogado imediatamente?
Depois de emitido, o JWT continua válido até expirar e não pode ser revogado diretamente. As opções são: 1) definir uma expiração curta, como 15 minutos, junto com um refresh token; 2) usar uma lista de bloqueio, embora isso elimine a vantagem de não manter estado; ou 3) adotar Session ou uma solução híbrida. Na maioria dos casos, uma expiração curta com refresh token é suficiente.
Como otimizar o desempenho de Session?
Como Session exige uma consulta ao banco de dados a cada requisição, ela pode virar um gargalo. Para otimizar: 1) armazene a Session no Redis, que é muito mais rápido que um banco tradicional; 2) guarde apenas o ID do usuário; 3) crie índices para sessionToken e expires; e 4) remova Sessions expiradas periodicamente.
Como configurar JWT e Session no NextAuth.js?
Para JWT, use session: { strategy: "jwt", maxAge: 30 dias } e adicione os dados do usuário em callbacks.jwt. Para Session, use PrismaAdapter e session: { strategy: "database" }, além de criar a tabela Session. Uma implantação no Edge Runtime exige separar as configurações.
Como tratar a expiração da sessão e melhorar a experiência do usuário?
Com JWT, use um Access Token de 15 minutos e um Refresh Token de 30 dias para fazer a renovação silenciosa antes da expiração. Com Session, use uma sessão deslizante, configurada por updateAge, para prolongá-la enquanto houver atividade. Evite que o usuário seja desconectado no meio do preenchimento de um formulário e perca os dados.
Quais são as novas tendências de JWT e Session em 2025?
Soluções híbridas estão se tornando comuns: JWT para autenticação rápida de API e Session registrada no banco de dados para manter o controle. O Edge Runtime favorece JWT, especialmente na autenticação via middleware. Passkeys e WebAuthn também estão ganhando espaço, e o NextAuth.js v5 já oferece suporte. Escolha com base nas necessidades reais e evite complexidade desnecessária.

15 min de leitura · Publicado em: 19 dez 2025 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog