Next.js: Autenticação & Autorização

Última atualização: 2026-08-26

Adicionar autenticação a uma aplicação full-stack é como instalar um sistema de controle de acesso em um prédio de escritórios — você precisa de controle preciso sobre quem pode entrar e quais andares podem acessar.

1. O Que Você Vai Aprender



2. Uma História Real de um Desenvolvedor Full-Stack

(1) Ponto de Dor: Não percebemos que faltava autenticação até pouco antes do lançamento

Alice trabalha em uma startup SaaS de 15 pessoas. Ela desenvolveu o "TaskFlow", uma plataforma de colaboração em equipe completa, usando Next.js 16. No entanto, durante uma auditoria de segurança antes do lançamento, Bob, o líder técnico, apontou:

"Sua página /dashboard está acessível para qualquer pessoa; o endpoint da API não tem autenticação por token; os dados do usuário estão expostos em texto puro."

O relatório de auditoria lista os seguintes problemas:

Problema Escopo Nível de Risco
Sem Página de Login Todas as Rotas 🔴 Alto
Rota API: Sem Autenticação /api/projects/* 🔴 Alto
Sem distinção de funções Todos os usuários podem ver o painel admin 🟡 Médio
Sessão nunca expira Login único e permanece logado para sempre 🔴 Alto

(2) A Solução com Auth.js + Clerk

Implemente o fluxo de autenticação padrão usando Auth.js v5, com Clerk como alternativa de configuração zero.

TS
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub,
    Google,
    Credentials({
      credentials: { email: {}, password: {} },
      async authorize(credentials) {
        const user = { id: '1', name: 'Alice', email: 'alice@taskflow.io', role: 'admin' }
        return credentials.email === 'alice@taskflow.io' && credentials.password === 'pass123' ? user : null
      }
    })
  ],
  callbacks: { session: ({ session, token }) => ({ ...session, user: { ...session.user, role: token.role } }) }
})

(3) Ganhos

Dimensão Antes da Implementação Depois da Implementação
Acessar /dashboard sem login Acessível Redirecionado para página de login
Segurança do Endpoint API Sem Validação Validação JWT com getToken()
Controle de Funções Nenhum Três níveis: admin, editor, viewer
Expiração de Sessão Permanente Expira automaticamente após 30 dias
Tempo de Integração Auth.js: 2h / Clerk: 30min


3. Integração de Provedores com Auth.js v5 (NextAuth)

(1) Arquitetura de Provedores

A camada de abstração de Provider no Auth.js v5 permite integrar múltiplas fontes de autenticação através de uma interface unificada:

100%
graph TB
    A[Requisição de Autenticação] --> B{Route Handler do NextAuth}
    B --> C[Credentials<br/>Email+Senha]
    B --> D[OAuth<br/>GitHub / Google]
    B --> E[Outro Provider<br/>Auth0 / Azure AD]
    C --> F[JWT Callback<br/>token + session]
    D --> F
    E --> F
    F --> G[Sessão Retornada ao cliente]
    G --> H[Verificação no Middleware]
    H --> I[Página Protegida]
    H --> J[Página Pública]

    style B fill:#cce5ff
    style F fill:#d4edda
Tipo de Provider Complexidade de Implementação Experiência do Usuário Cenários Aplicáveis
Credentials Média (requer página de login personalizada) Formulário padrão Sistema de contas próprio
GitHub OAuth Baixa Login com um clique Ferramentas para desenvolvedores
Google OAuth Baixa Login com um clique Para usuários em geral
OIDC / SAML Alta SSO Empresarial Intranet corporativa

(2) Instalação e Inicialização

BASH
npm install next-auth@beta
npx auth secret    # Gerar AUTH_SECRET
TS
// auth.ts — Configuração Centralizada de Autenticação
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET! }),
    Google({ clientId: process.env.GOOGLE_ID!, clientSecret: process.env.GOOGLE_SECRET! })
  ]
})

▶ Exemplo: Configuração Completa de um Provider Credentials

Saída:

TEXT 📖 Somente leitura
TypeScript code executed successfully.
TS
// auth.ts — Credentials + OAuth Misto
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import GitHub from 'next-auth/providers/github'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Senha', type: 'password' }
      },
      async authorize(credentials) {
        const { email, password } = credentials as { email: string; password: string }
        // Projeto Real: Consultar Banco de Dados
        const user = { id: '1', name: 'Alice', email, role: 'admin' }
        if (email === 'admin@taskflow.io' && password === 'admin123') return user
        return null
      }
    }),
    GitHub
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) token.role = (user as any).role
      return token
    },
    async session({ session, token }) {
      session.user.role = token.role as string
      return session
    }
  }
})

Saída:

TEXT 📖 Somente leitura
TypeScript configuration loaded successfully.
💡 Dica: Se a função authorize retornar null, significa que o login falhou, e o Auth.js retorna automaticamente uma resposta 401.



4. Gerenciamento de Sessão: JWT vs Database

(1) Comparação das Duas Estratégias

O Auth.js v5 suporta duas estratégias de armazenamento de sessão:

Dimensão JWT (padrão) Database
Local de Armazenamento JWT criptografado em um cookie Tabela Session no banco de dados
Sobrecarga de Consulta Zero (sem consultas ao BD) Uma consulta ao BD por requisição
Expiração Imediata Depende do Tempo de Expiração do JWT Pode Ser Revogada Imediatamente
Escalabilidade Sem necessidade de banco de dados Requer um ORM como Prisma
Escala Adequada Aplicações Pequenas e Médias Grandes Aplicações Empresariais
100%
graph LR
    subgraph Padrão JWT
        A1[Login] --> B1[Gerar JWT<br/>com user + role]
        B1 --> C1[Gravar Cookie]
        C1 --> D1[Requisição → middleware<br/>Descriptografar JWT → Verificação]
    end
    subgraph Padrão Database
        A2[Login] --> B2[Criar Session<br/>Gravar no BD]
        B2 --> C2[Session ID → Cookie]
        C2 --> D2[Requisição → middleware<br/>Buscar no BD → Verificação]
    end

    style A1 fill:#d4edda
    style A2 fill:#cce5ff

(2) Configuração de Sessão com Database

TS
// auth.ts — Database Session + Prisma
import NextAuth from 'next-auth'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: 'database' },
  providers: [GitHub, Google]
})

▶ Exemplo: Recuperando uma Sessão e Exibindo Informações do Usuário

Saída:

TEXT 📖 Somente leitura
TypeScript module executes successfully.
TSX
// app/dashboard/page.tsx — Recuperação de Sessão no Servidor
import { auth } from '@/auth'

export default async function DashboardPage() {
  const session = await auth()

  if (!session?.user) return <p>Por favor, faça login primeiro</p>

  return (
    <div>
      <h1>Bem-vindo de volta, {session.user.name}</h1>
      <p>Email: {session.user.email}</p>
      <p>Função: {session.user.role}</p>
      <img src={session.user.image!} alt="avatar" width={48} height={48} />
    </div>
  )
}
💻 Saída:

TEXT 📖 Somente leitura
<h1>Bem-vindo de volta, Alice</h1>
<p>Email: alice@taskflow.io</p>
<p>Função: admin</p>

Saída:

TEXT 📖 Somente leitura
<h1>Welcome back, Alice</h1>
<p>Email: alice@taskflow.io</p>
<p>Role: admin</p>


5. Proteção de Rotas com Middleware

(1) Modo de Configuração do Matcher

middleware.ts intercepta a requisição antes que ela chegue à página para verificar a validade da sessão:

100%
graph TB
    A[Usuário Requisita /dashboard/*] --> B{middleware.ts}
    B -->|Sessão Válida| C[Liberado → page.tsx]
    B -->|Sem Sessão| D[Redirecionar /login]
    D --> E{Caminhos Públicos?}
    E -->|/api/auth/*| F[Liberado]
    E -->|/_next/*| F
    E -->|/favicon.ico| F

    style B fill:#fff3cd
    style C fill:#d4edda
    style D fill:#f8d7da
Padrão Matcher Caminho Correspondente Descrição
/dashboard/:path* /dashboard/* Proteger o Dashboard
/api/projects/:path* /api/projects/* Proteger API
`/((?!auth _next favicon).*)`

(2) Implementação Completa do Middleware

TS
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'

export default auth((req) => {
  const { pathname } = req.nextUrl
  const isLoggedIn = !!req.auth
  const isPublicPath = pathname.startsWith('/login') ||
    pathname.startsWith('/register') ||
    pathname.startsWith('/api/auth')

  if (!isLoggedIn && !isPublicPath) {
    return NextResponse.redirect(new URL('/login', req.url))
  }

  // RBAC: Impedir que não-admin acesse /admin
  if (pathname.startsWith('/admin') && req.auth?.user?.role !== 'admin') {
    return NextResponse.redirect(new URL('/dashboard', req.url))
  }

  return NextResponse.next()
})

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|images).*)']
}

▶ Exemplo: Adicionando Proteção de Autenticação a uma Rota de API

Saída:

TEXT 📖 Somente leitura
Middleware intercepts requests and redirects based on conditions.
TS
// app/api/projects/route.ts — API Protegida
import { getToken } from 'next-auth/jwt'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const token = await getToken({ req })

  if (!token) {
    return NextResponse.json({ error: 'Não está logado' }, { status: 401 })
  }

  // token.role vem do callback JWT
  if (token.role !== 'admin' && token.role !== 'editor') {
    return NextResponse.json({ error: 'Permissões insuficientes' }, { status: 403 })
  }

  return NextResponse.json({ projects: [{ id: 1, name: 'TaskFlow' }] })
}

Saída:

TEXT 📖 Somente leitura
GET app/api/projects/route.ts → Verifica token de autenticação, retorna dados protegidos como JSON.
🔥 Erro Comum: getToken() requer que a variável de ambiente AUTH_SECRET esteja configurada; caso contrário, retorna null.



6. Autenticação com Clerk (Terceiros)

(1) Comparação de Funcionalidades do Clerk

Funcionalidade Auth.js Clerk
Complexidade de Instalação Média (requer configuração de Provider) Baixa (npm + variáveis de ambiente)
Componentes de UI Página de Login Personalizada <SignIn /> / <SignUp /> Prontos para Uso
Autenticação multifator Requer integração manual Suporte integrado
Limites do Plano Gratuito Nenhum 5.000 MAU (Grátis)
UI Personalizada Liberdade total Muitas restrições

(2) Passos para Integrar o Clerk

BASH
npm install @clerk/nextjs
# No .env.local, adicione:
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=...
# CLERK_SECRET_KEY=...
TSX
// app/layout.tsx — ClerkProvider Envolvendo o Layout Raiz
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="pt">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}

▶ Exemplo: Página de Login com Clerk + Página Protegida

Saída:

TEXT 📖 Somente leitura
Renders the RootLayout component UI.
TSX
// app/page.tsx — Página de Login
import { SignIn, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'

export default function HomePage() {
  return (
    <div>
      <SignedOut>
        <SignIn routing="hash" />
      </SignedOut>
      <SignedIn>
        <div>
          <UserButton afterSignOutUrl="/" />
          <h1>Bem-vindo ao TaskFlow</h1>
        </div>
      </SignedIn>
    </div>
  )
}

Saída:

TEXT 📖 Somente leitura
Renders: Bem-vindo ao TaskFlow
Visible text: Bem-vindo ao TaskFlow
TSX
// app/dashboard/page.tsx — Proteção com Clerk auth()
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const { userId } = auth()

  if (!userId) redirect('/sign-in')

  return <div>Dashboard — UserID: {userId}</div>
}
💡 Dica: O auth() do Clerk é uma função do lado do servidor; você não precisa de 'use client' ao usá-lo em um Server Component.

(3) Middleware do Clerk

TS
// middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server'

export default clerkMiddleware()

export const config = {
  matcher: ['/((?!_next|sign-in|sign-up|favicon.ico).*)']
}


7. RBAC — Controle de Acesso Baseado em Funções

(1) Design do Modelo de Funções

100%
graph TB
    A[Usuário] --> B{Função}
    B --> C[admin<br/>Acesso Total]
    B --> D[editor<br/>Leitura e Escrita de Projetos]
    B --> E[viewer<br/>Somente Leitura]

    C --> F[Criar/Excluir Itens]
    C --> G[Gerenciar Equipe]
    C --> H[Alterar Configurações]
    D --> I[Editar Tarefas]
    D --> J[Adicionar Comentários]
    E --> K[Ver Dashboard]
    E --> L[Ler Documentos]

    style C fill:#d4edda
    style D fill:#cce5ff
    style E fill:#f8d7da
Função Nível de Permissão Páginas Acessíveis
admin 100 Todas (incluindo /admin)
editor 50 /dashboard, /projects (editável)
viewer 20 /dashboard (somente leitura)

▶ Exemplo: Componente de Verificação de Permissão RBAC

Saída:

TEXT 📖 Somente leitura
Diagram: Usuário; admin Acesso Total; editor Leitura e Escrita de Projetos; viewer Somente Leitura; Criar/Excluir Itens; Gerenciar Equipe.
TSX
// components/PermissionGuard.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'

type Role = 'admin' | 'editor' | 'viewer'

const roleHierarchy: Record<Role, number> = { admin: 100, editor: 50, viewer: 20 }

export async function PermissionGuard({
  children,
  minRole
}: {
  children: React.ReactNode
  minRole: Role
}) {
  const session = await auth()
  const userRole = (session?.user?.role as Role) || 'viewer'

  if (roleHierarchy[userRole] < roleHierarchy[minRole]) {
    redirect('/dashboard')
  }

  return <>{children}</>
}

Saída:

TEXT 📖 Somente leitura
Renders the PermissionGuard component UI.
TSX
// app/admin/page.tsx — Uso do PermissionGuard
import { PermissionGuard } from '@/components/PermissionGuard'

export default function AdminPage() {
  return (
    <PermissionGuard minRole="admin">
      <h1>Console de Gerenciamento</h1>
      <p>Esta página só pode ser vista por admin.</p>
    </PermissionGuard>
  )
}


8. Exemplo Completo: Implementação Abrangente de Autenticação Multi-Provedor + RBAC

TSX
// app/dashboard/layout.tsx — Layout Protegido + RBAC
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { PermissionGuard } from '@/components/PermissionGuard'

export default async function DashboardLayout({
  children,
  analytics,
  team
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  const session = await auth()

  if (!session) redirect('/login')

  const user = session.user!

  return (
    <div>
      <header>
        <h1>TaskFlow</h1>
        <p>Bem-vindo, {user.name} ({user.role})</p>
        <nav>
          <a href="/dashboard">Visão Geral</a>
          {user.role === 'admin' && <a href="/admin">Gerenciamento</a>}
          <a href="/api/auth/signout">Sair</a>
        </nav>
      </header>
      <div style={{ display: 'flex', gap: '2rem' }}>
        <main>{children}</main>
        <aside>
          {analytics}
          <PermissionGuard minRole="editor">{team}</PermissionGuard>
        </aside>
      </div>
    </div>
  )
}
TS
// app/api/projects/[id]/route.ts — Proteção Completa de API
import { getToken } from 'next-auth/jwt'
import { NextRequest, NextResponse } from 'next/server'

const roles = { admin: 100, editor: 50, viewer: 20 }

export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  const token = await getToken({ req })
  const { id } = await params

  if (!token) return NextResponse.json({ error: 'não autorizado' }, { status: 401 })
  if (roles[token.role as keyof typeof roles] < 50) {
    return NextResponse.json({ error: 'proibido' }, { status: 403 })
  }

  // Excluir o Item Realmente
  return NextResponse.json({ id, deleted: true })
}
💻 Saída (requisição DELETE sem login):

JSON
{ "error": "não autorizado" }
💻 Saída (requisição DELETE pela função "viewer"):

JSON
{ "error": "proibido" }

❓ Perguntas Frequentes

P: Qual é a diferença entre Auth.js v4 (NextAuth) e v5? R: A v5 usa o Route Handler do App Router (app/api/auth/[...nextauth]/route.ts), suporta recuperar a sessão diretamente em Server Components via auth(), e não requer mais getSession() ou SessionProvider.

P: Qual devo escolher: sessões JWT ou sessões de banco de dados? R: Para aplicações pequenas ou que não exigem revogação imediata de sessão, escolha JWT (zero consultas ao BD). Para aplicações empresariais que exigem logout imediato do usuário ou gerenciamento de múltiplos dispositivos, escolha sessões de banco de dados. No modo JWT, a revogação de sessão deve esperar até que o JWT expire (30 dias por padrão).

P: Quando devo escolher Clerk e quando devo escolher Auth.js? R: Se você precisa lançar rapidamente e não quer construir sua própria UI → Clerk (integração em 30 minutos). Se você precisa de uma UI totalmente personalizável e quer construir seu próprio sistema de contas → Auth.js. O plano gratuito do Clerk é limitado a 5.000 MAU; acima disso, custa €25/mês.

P: Qual é a diferença entre auth() e getToken() no Middleware? R: auth() é uma função wrapper do Auth.js v5 que retorna um objeto Session completo. getToken() é de next-auth/jwt e analisa apenas tokens JWT, oferecendo melhor desempenho. Recomendamos auth() por sua simplicidade no middleware; para rotas de API, recomendamos getToken() por sua leveza.

P: Como protejo páginas exportadas estaticamente? R: Sites estáticos construídos com output: 'export' não podem usar middleware (que requer o Node.js runtime). Nesse caso, você deve usar o hook useSession() em um componente cliente para verificar o status de login, ou usar a renderização condicional <SignedIn> / <SignedOut> do Clerk.


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie um novo projeto Next.js, integre o Provider GitHub do Auth.js v5 e exiba o avatar e o endereço de email do usuário na página /dashboard.

  2. Exercício Avançado (⭐⭐): Adicione lógica RBAC ao middleware: admin pode acessar /admin/*, e editor pode acessar a interface de edição /projects/*; retorne uma página 403 se qualquer outra função tentar acessar esses recursos.

  3. Desafio (⭐⭐⭐): Combinando Auth.js (Provider Credentials) e Prisma: O usuário insere email e senha na página de login → authorize consulta o banco de dados para validar → Grava role e orgId no JWT → Middleware redireciona para o workspace da organização correspondente com base no orgId.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%