Aplicações modernas funcionam raramente isoladas. Muitas vezes temos um frontend que lida com a experiência do usuário, e um backend que concentra as regras de negócio e o acesso a dados sensíveis.
Garantir que apenas usuários autenticados consigam acessar esses recursos é fundamental.
Neste artigo, vamos construir uma solução que utiliza Next.js como frontend, integrando a autenticação com o Microsoft Entra ID (antigo Azure Active Directory), e um backend em Node/TypeScript que valida tokens JWT emitidos pelo Entra ID antes de liberar o acesso a rotas protegidas.
Arquitetura Geral
A solução pode ser entendida em cinco passos principais:
- O usuário acessa a aplicação em Next.js e inicia o login.
- O NextAuth.js redireciona o usuário para o Microsoft Entra ID.
- Após a autenticação, o Entra ID retorna um ID Token e um Access Token para o Next.js.
- O frontend envia o Access Token ao backend, no header
Authorization: Bearer <token>
. - O backend valida o token contra o serviço de chaves públicas (JWKS) do Entra ID.
- Se válido → rota protegida é acessada.
- Se inválido/expirado → resposta
401 Unauthorized
.
1. Configuração no Entra ID
No Azure Portal:
- Vá em Microsoft Entra ID → App registrations → New registration.
-
Preencha os campos:
- Name:
nextjs-auth-app
- Redirect URI:
http://localhost:3000/api/auth/callback
- Ative ID tokens e Access tokens.
- Name:
-
Salve as credenciais:
- Application (client) ID
- Directory (tenant) ID
- Client Secret (criado manualmente em Certificates & secrets).
2. Frontend Next.js com NextAuth
Instalação
npm install next-auth @azure/msal-node
Variáveis de ambiente
Arquivo .env.local
:
AZURE_AD_CLIENT_ID=<Application ID> # ID da aplicação registrada no Entra ID AZURE_AD_CLIENT_SECRET=<Client Secret> # Segredo do app (não expor no frontend) AZURE_AD_TENANT_ID=<Directory ID> # Tenant (diretório da organização) NEXTAUTH_URL=http://localhost:3000 # URL da aplicação Next.js NEXTAUTH_SECRET=<chave-aleatória> # Chave para assinar JWT de sessão
Configuração do NextAuth
import NextAuth from "next-auth"; import AzureADProvider from "next-auth/providers/azure-ad"; // Configuração principal do NextAuth export default NextAuth({ providers: [ // Habilita login com Microsoft Entra ID (Azure AD) AzureADProvider({ clientId: process.env.AZURE_AD_CLIENT_ID!, // ID do app registrado no Entra ID clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,// Segredo do cliente (guardado no backend) tenantId: process.env.AZURE_AD_TENANT_ID!, // ID do diretório (tenant) }), ], session: { strategy: "jwt" }, // Sessões baseadas em JWT (sem persistência no BD) callbacks: { // Callback executado ao criar/atualizar JWT async jwt({ token, account }) { if (account) token.accessToken = account.access_token; // Armazena o accessToken no token return token; }, // Callback executado ao montar a sessão async session({ session, token }) { session.accessToken = token.accessToken as string; // Inclui o accessToken na sessão return session; }, }, });
Componente de Login/Logout (AuthButton.tsx
)
"use client"; // Necessário porque usamos interações de browser (signIn/signOut) import { signIn, signOut, useSession } from "next-auth/react"; export default function AuthButton() { const { data: session } = useSession(); // Hook que recupera a sessão atual if (session) { // Caso o usuário esteja logado return ( <button onClick={() => signOut()}> Sair ({session.user?.name}) {/* Mostra o nome do usuário logado */} </button> ); } // Caso não esteja logado return ( <button onClick={() => signIn("azure-ad")}> Entrar com Entra ID {/* Chama fluxo de login no Entra ID */} </button> ); }
Tela Principal (page.tsx
)
import AuthButton from "./components/AuthButton"; // Importa o botão de login/logout import Link from "next/link"; export default function Home() { return ( <main style={{ padding: "2rem" }}> <h1>🔐 Demo de Autenticação com Entra ID</h1> {/* Botão que mostra "Entrar" ou "Sair", dependendo da sessão */} <AuthButton /> <hr style={{ margin: "2rem 0" }} /> {/* Link para acessar página protegida */} <Link href="/protected"> <button>Acessar página protegida</button> </Link> </main> ); }
3. Server Component: Página Protegida
import { authOptions } from "../api/auth/[...nextauth]/route"; import { getServerSession } from "next-auth"; export default async function ProtectedPage() { // Recupera a sessão no lado do servidor const session = await getServerSession(authOptions); if (!session) { // Caso o usuário não esteja autenticado return <p>Você precisa estar logado.</p>; } // Faz requisição ao backend protegido enviando o accessToken no header const res = await fetch("http://localhost:4000/api/secure-data", { headers: { Authorization: `Bearer ${session.accessToken}`, // Token nunca vai para o client }, cache: "no-store", // Garante que não será cacheado }); const data = await res.json(); return ( <div> <h2>Olá, {session.user?.name}</h2> {/* Nome do usuário logado */} <pre>{JSON.stringify(data, null, 2)}</pre> {/* Dados retornados da API */} </div> ); }
4. Backend Node/TypeScript
Dependências
npm install express jwks-rsa express-jwt
Código (src/index.ts
)
import express from "express"; import { expressjwt as jwt } from "express-jwt"; import jwksRsa from "jwks-rsa"; const app = express(); const port = 4000; // Middleware que valida o JWT recebido const checkJwt = jwt({ // Define a forma de obter as chaves públicas do Entra ID secret: jwksRsa.expressJwtSecret({ cache: true, // Cache para não buscar chave toda vez rateLimit: true, // Limite de requisições para JWKS jwksRequestsPerMinute: 10, // Máx. 10 req/min jwksUri: `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}/discovery/v2.0/keys`, }) as any, audience: process.env.AZURE_AD_CLIENT_ID, // Valida se o token é para este app issuer: `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}/v2.0`, // Origem do token algorithms: ["RS256"], // Algoritmo esperado }); // Rota protegida: só acessa se o JWT for válido app.get("/api/secure-data", checkJwt, (req, res) => { res.json({ message: "Autenticado ✅", claims: (req as any).auth, // Retorna as claims do token decodificado }); }); // Inicializa o servidor app.listen(port, () => console.log(`🚀 Backend rodando em http://localhost:${port}`) );
Boas Práticas
- Proteção de secrets: nunca exponha
clientSecret
no frontend. - Sessões seguras: use cookies
httpOnly
ou JWT fornecidos pelo NextAuth. - Validação rigorosa: configure corretamente
audience
eissuer
. - Observabilidade: registre logs de falhas de autenticação.
- Escalabilidade: esse padrão permite múltiplos serviços validando tokens sem depender do frontend.
Conclusão
Com essa arquitetura, garantimos um fluxo seguro de ponta a ponta:
- O Next.js gerencia autenticação e sessão do usuário.
- O backend Node/TS valida tokens antes de liberar dados sensíveis.
- O Microsoft Entra ID centraliza a identidade, oferecendo segurança corporativa.
Esse modelo é flexível e pode ser expandido para microsserviços ou integração com APIs da Microsoft (Graph API).
Referências
Microsoft Corporation. (2024a). Microsoft identity platform documentation. Microsoft Learn.
https://learn.microsoft.com/en-us/azure/active-directory/develop/Microsoft Corporation. (2024b). Microsoft Entra ID: Overview. Microsoft Learn.
https://learn.microsoft.com/en-us/entra/identity/Microsoft Corporation. (2024c). Configure NextAuth.js with Azure AD. Microsoft Learn.
https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spaNextAuth.js. (2024). NextAuth.js Documentation.
https://next-auth.js.org/Auth0 Inc. (2024). Introduction to JWTs.
https://auth0.com/intro-to-jwt
Top comments (0)