Autenticação Segura com Microsoft Entra ID para desenvolvedores Typescript
Aplicações modernas funcionam raramente isoladas. Muitas vezes temos um frontend que lida com a...
Publicado originalmente no dev.to (abre em nova aba) por Cláudio Rapôso. Comentários e reações ficam por lá.

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
clientSecretno frontend. -
Sessões seguras: use cookies
httpOnlyou JWT fornecidos pelo NextAuth. -
Validação rigorosa: configure corretamente
audienceeissuer. - 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
