Tutorial · Node.js

Login com certificado digital ICP-Brasil em Node.js

Como transformar um certificado e-CPF ou e-CNPJ apresentado pelo navegador em uma sessão autenticada numa aplicação Node.js — do handshake mTLS no proxy até a criação da sessão no Express.

Node.js · Express NGINX na frente Leitura: ~9 min
Neste guia
  1. A arquitetura em uma imagem
  2. Terminando o mTLS no proxy
  3. Lendo o certificado no Express
  4. O passo que não pode faltar: validar
  5. Criando a sessão
  6. O que fica de manutenção

A arquitetura em uma imagem

Node.js pode terminar TLS diretamente, mas em produção quase todo mundo coloca um proxy reverso (NGINX) na frente para cuidar do TLS e do mTLS, deixando a aplicação focada na lógica. É esse o desenho que vamos seguir, porque é o mais comum e o mais robusto:

Fluxo. Navegador (apresenta o e-CPF) → NGINX (faz o handshake mTLS e valida a cadeia) → repassa os dados do certificado em cabeçalhos → Node/Express (lê os cabeçalhos, revalida, cria a sessão). O NGINX faz o trabalho pesado de TLS; o Node decide quem entra.

Se você ainda não tem o mTLS configurado no NGINX, o primeiro artigo desta série cobre isso em detalhe — aqui vamos assumir que o handshake já acontece e focar no lado Node.

Terminando o mTLS no proxy

No bloco server do NGINX, além de exigir o certificado do cliente, você repassa o resultado e os dados para o backend via cabeçalhos:

nginx.conf — repassando ao Node
location / {
    proxy_pass http://127.0.0.1:3000;

    # resultado da validação feita pelo NGINX
    proxy_set_header X-SSL-Verify  $ssl_client_verify;
    # dados do titular (Subject DN)
    proxy_set_header X-SSL-Subject $ssl_client_s_dn;
    # certificado completo, caso o Node precise reprocessar
    proxy_set_header X-SSL-Cert    $ssl_client_escaped_cert;
}
Segurança. Esses cabeçalhos só são confiáveis porque vêm do seu proxy. Garanta que o Node aceite conexões apenas do NGINX (bind em 127.0.0.1 ou rede interna) — senão alguém pode forjar X-SSL-Verify: SUCCESS e burlar tudo. É o erro de segurança mais comum nesse setup.

Lendo o certificado no Express

No Node, um middleware lê os cabeçalhos e barra logo de cara quem não passou na validação do NGINX:

javascript — middleware de autenticação
const express = require("express");
const app = express();

function autenticarCertificado(req, res, next) {
  const verify = req.headers["x-ssl-verify"];

  // o NGINX já validou a cadeia; se não passou, nem seguimos
  if (verify !== "SUCCESS") {
    return res.status(401).json({ erro: "Certificado não reconhecido" });
  }

  const subject = req.headers["x-ssl-subject"];
  // aqui vem a parte trabalhosa: extrair CPF/CNPJ e nome
  // dos OIDs proprietários da ICP-Brasil dentro do certificado
  req.titular = parseIcpBrasil(subject, req.headers["x-ssl-cert"]);

  if (!req.titular) {
    return res.status(401).json({ erro: "Dados do certificado inválidos" });
  }
  next();
}

app.post("/login", autenticarCertificado, (req, res) => {
  // req.titular já tem CPF/CNPJ e nome
  const sessao = criarSessao(req.titular);
  res.json({ ok: true, sessao });
});

Parece simples — e o esqueleto é. O peso está escondido dentro de duas funções: parseIcpBrasil() e a revalidação. É onde a maior parte do tempo de desenvolvimento vai.

O passo que não pode faltar: validar (de novo)

O NGINX valida a cadeia no handshake, mas há duas coisas que você ainda precisa tratar do lado da aplicação:

O parseIcpBrasil() não é trivial. Ele precisa detectar se é e-CPF ou e-CNPJ, aplicar o parsing correto de cada um, cobrir A1 e A3, lidar com variações de preenchimento entre as diferentes ACs e com revisões da normativa. É pouco código e muita regra de negócio frágil.

Criando a sessão

Com o titular validado e os dados extraídos, o resto é o seu fluxo de sempre: gerar um token de sessão (JWT, cookie assinado, o que você já usa), associar ao CPF/CNPJ e seguir. A parte específica de certificado termina aqui — daqui para frente é a sua aplicação como qualquer outra.

O ponto a reter: o "difícil" do login por certificado não é o Express nem a sessão — isso você já sabe fazer. O difícil é tudo que acontece antes de req.titular existir de forma confiável: o mTLS, a revogação e o parsing. E, ao contrário do resto da sua aplicação, essa parte depende de uma norma externa que muda sem te avisar.

O que fica de manutenção

Depois de no ar, o código Node praticamente não muda — mas o ecossistema ICP-Brasil embaixo dele, sim:

Nenhuma dessas tarefas é sobre Node ou Express. São sobre manter uma camada de identidade viva, sensível a segurança, atrelada a uma norma que não é sua — indefinidamente.

Ou receba req.titular pronto

O login por certificado sem a parte difícil

Com o IHub-Auth, seu backend Node recebe CPF, CNPJ e nome do titular já validados via verificação server-to-server — sem terminar mTLS, sem checar CRL, sem fazer parsing de OID. A cadeia ICP-Brasil completa fica atualizada do nosso lado. Você inclui um script, configura o token e trata só a sessão, que é a parte que você já domina.