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)