core/ZITADEL_SETUP.md

8.1 KiB

Zitadel + Next.js App Router Integration Setup

Guia corporativo atuando como Especialista em Engenharia de Software e DevOps para inicialização do ecossistema Zitadel on-premise (Baremetal/Binário direto) integrado à um ambiente moderno Next.js.

Parte 1: Setup do Serviço de Autenticação (Zitadel)

1. Download e Instalação (Binário Oficial)

O Zitadel é distribuído em um binário em Go altamente otimizado. Para ambientes baseados em Linux/MacOS:

# Definir a versão alvo
export ZITADEL_VERSION="v2.66.3" # ou a tag latest estável

# Download macOS (usar darwin_arm64 para Apple Silicon ou darwin_amd64 para Intel)
# Download Linux (usar linux_amd64 ou linux_arm64)
curl -sLO "https://github.com/zitadel/zitadel/releases/download/${ZITADEL_VERSION}/zitadel_Linux_x86_64.tar.gz"

# Extrair
tar -xvf zitadel_Linux_x86_64.tar.gz

# Mover binário pro path
sudo mv zitadel /usr/local/bin/

2. Configuração Básica e Local (Sem TLS / Insecure)

Crie um arquivo config.yaml voltado para acoplamento interno. Ele especifica conexões sem segredos e expõe a interface sem demandar certificados TLS para localhost.

config.yaml:

ExternalSecure: false
Port: 8080

# Conexão com sua base PostgreSQL limpa inicializada (Pode ser local/Docker)
Database:
  postgres:
    Host: localhost
    Port: 5432
    Database: zitadel
    User:
      Username: "postgres"
      Password: "your-password"
      SSL:
        Mode: disable
    Admin:
      Username: "postgres"
      Password: "your-password"
      SSL:
        Mode: disable

3. Setup e Start

A injeção dos esquemas base e a inicialização do container de autenticação:

# 1. Aplicar migrações ao PostgreSQL provisionado
zitadel setup --masterkey "a-32-byte-master-key-must-be-set" --config config.yaml

# 2. Levantar o Listener HTTP
zitadel start --masterkey "a-32-byte-master-key-must-be-set" --config config.yaml

O setup inicial gerará um log contendo as informações do usuário machine primário (zitadel-admin). Guarde este log/json.

4. Gerar chaves (Service User / Machine Key)

  1. Acesse http://localhost:8080/ui/console.
  2. Vá em Projects > (Seu projeto) > Service Users.
  3. Crie um novo. Confirme.
  4. Na página de gerenciar esse Service User, vá em Keys e adicione uma nova.
  5. Selecione formato JSON. Ele fará o download da machinekey.json. Guarde esse documento num secrets/ do Next.

Parte 2: Dashboard Next.js (App Router + Tailwind)

1. Inicializando Projetos

npx create-next-app@latest admin-dashboard --typescript --tailwind --eslint --app
cd admin-dashboard
npm install @zitadel/nextjs @zitadel/server

2. Middleware de Proteção

Crie um arquivo em src/middleware.ts para capturar a sessão sem bloqueios pesados.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // A SDK usa os tokens armazenados em cookies.
  const hasZitadelCookie = request.cookies.some((c) => c.name.includes("zitadel"));

  if (!hasZitadelCookie && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/api/auth/signin", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*"],
};

3. Server Component de Dashboard

Interface estilizada de alto nível para exibição do IAM e profile contextual, injetante de tailwind corporativo de forma nativa e sem use client.

src/app/dashboard/page.tsx

import { getSession } from '@zitadel/nextjs';
// A API Wrapper SDK para o client Admin gRPC/REST do Zitadel
import { createZitadelClient } from '@zitadel/server'; 
import fs from 'fs/promises';

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

  if (!session?.user) {
    return <p className="text-zinc-400 font-mono">Não autorizado.</p>;
  }

  // 1. Instanciando Client IAM via Machine JSON
  const keyFile = await fs.readFile(process.cwd() + '/secrets/machinekey.json', 'utf8');
  const zitadelClient = createZitadelClient({
    token: keyFile,
    apiUrl: process.env.ZITADEL_API_URL!, 
  });

  // 2. Coletando Orgs
  let organizations: any[] = [];
  try {
    const { orgs } = await zitadelClient.management.listOrgs({});
    organizations = orgs || [];
  } catch (error) {
    console.error("Falha ao buscar organizações", error);
  }

  return (
    <div className="min-h-screen bg-zinc-950 text-zinc-100 flex flex-col p-8 font-sans">
      <div className="max-w-5xl w-full mx-auto space-y-6">
        
        <header className="border-b border-zinc-800 pb-4 mb-8">
          <h1 className="text-3xl font-bold tracking-tight">Identity Control Plane</h1>
          <p className="text-zinc-500 mt-2">Visão Administrativa Zitadel</p>
        </header>

        <section className="bg-zinc-900 rounded-xl border border-zinc-800 p-6 shadow-2xl">
          <h2 className="text-xl font-semibold mb-4 text-white">Perfil Atual</h2>
          <div className="grid grid-cols-2 gap-4">
            <div className="bg-black/50 p-4 rounded-lg">
              <span className="text-xs text-zinc-500 uppercase">Usuário</span>
              <p className="font-medium font-mono text-zinc-300 mt-1">{session.user.name}</p>
            </div>
            <div className="bg-black/50 p-4 rounded-lg">
              <span className="text-xs text-zinc-500 uppercase">E-mail ID</span>
              <p className="font-medium font-mono text-blue-400 mt-1">{session.user.email}</p>
            </div>
          </div>
        </section>

        <section className="bg-zinc-900 rounded-xl border border-zinc-800 p-6 shadow-2xl">
          <h2 className="text-xl font-semibold mb-4 flex items-center justify-between">
            <span>Organizações <span className="ml-2 text-xs bg-indigo-500/20 text-indigo-400 px-2 py-1 rounded-md">Service API</span></span>
          </h2>
          
          <ul className="space-y-3">
            {organizations.length === 0 ? (
              <li className="text-zinc-500 text-sm">Nenhuma organização encontrada sob este tenant.</li>
            ) : (
              organizations.map((org) => (
                <li key={org.id} className="flex justify-between items-center p-4 bg-black/40 rounded-lg border border-zinc-800 hover:border-indigo-500 transition-colors cursor-pointer">
                  <div className="flex flex-col">
                    <span className="font-medium text-zinc-200">{org.name}</span>
                    <span className="text-xs font-mono text-zinc-600 mt-1">ID: {org.id}</span>
                  </div>
                  <span className="text-xs px-3 py-1 bg-emerald-500/10 text-emerald-400 rounded-full border border-emerald-500/20">Active</span>
                </li>
              ))
            )}
          </ul>
        </section>

      </div>
    </div>
  );
}

Parte 3: Guia de Integração Ambiente Local

1. Preparação Local Next.js (.env.local)

Adicione o endpoint do provedor OIDC e credenciais do dashboard para sua integração funcionar na rota cliente:

# SDK NextAuth 
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="uma-string-secreta-gigante-aqui-random"

# Zitadel Configuration (A porta que você mapeou no config.yaml)
ZITADEL_ISSUER="http://localhost:8080"
ZITADEL_CLIENT_ID="AQUI_VAI_O_CLIENT_ID_DO_SEU_WEB_APP"
ZITADEL_CLIENT_SECRET="AQUI_VAI_O_SECRET_DO_WEB_APP"

# API Endpoint (Utilizado pelo Client JWT Server para as Orgs)
ZITADEL_API_URL="http://localhost:8080"

2. Rodando o Ecossistema Completamente Local

Para rodar os serviços paralelos no seu ambiente de terminal:

No Terminal 1 (O IAM Authority Zitadel):

./zitadel start --masterkey "a-32-byte-master-key-must-be-set" --config config.yaml

No Terminal 2 (O Dashboard Edge-side):

cd admin-dashboard
npm run dev

Essa arquitetura garante que a requisição de contexto do OIDC caia por trás dos painéis locais e que você utilize a machinekey (Service Account) internamente em rotas NodeJS sem jamais vazar a chave sensível da infraestrutura, além do Tailwind em Dark Mode proporcionar uma visualização profissional para auditoria de redes.