Como integrar validação de CPF em Electron para apps desktop

Aprenda a integrar a API de consulta de CPF em aplicações Electron desktop com comunicação segura entre main e renderer process.

Lucas Vieira
Lucas Vieira
··8 min de leitura
Como integrar validação de CPF em Electron para apps desktop

Para integrar validação de CPF em um app Electron, a chamada à API deve ocorrer exclusivamente no main process — nunca no renderer — usando IPC para transportar o resultado com segurança. A CPFHub.io expõe um endpoint GET https://api.cpfhub.io/cpf/{CPF} autenticado pelo header x-api-key, com latência de ~900ms. O safeStorage do Electron cuida do armazenamento criptografado da chave no sistema operacional host, e um cache SQLite local reduz consultas desnecessárias.


Arquitetura de seguranca no Electron

O Electron possui dois processos principais:

  • Main process -- Executa Node.js com acesso completo ao sistema. E aqui que a chave de API deve ficar e as chamadas HTTP devem acontecer.
  • Renderer process -- Executa o frontend (como um navegador). Deve ter acesso restrito via contextIsolation e preload scripts.

A regra de ouro: nunca exponha a chave de API no renderer process. Use IPC (Inter-Process Communication) para mediar a comunicação. Consulte a documentação oficial do Electron sobre segurança para uma visão completa das práticas recomendadas.


Configuração do main process

O main process configura a janela e registra os handlers IPC para consulta de CPF:

// main.js
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");

const CPFHUB_API_KEY = process.env.CPFHUB_API_KEY;
const API_URL = "https://api.cpfhub.io/cpf";
const TIMEOUT_MS = 10000;

async function consultarCPF(cpfNumero) {
    const cpfLimpo = cpfNumero.replace(/\D/g, "");

    if (cpfLimpo.length !== 11) {
    return { sucesso: false, erro: "CPF deve conter 11 digitos" };
    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);

    try {
    const response = await fetch(`${API_URL}/${cpfLimpo}`, {
    method: "GET",
    headers: {
    "x-api-key": CPFHUB_API_KEY,
    "Accept": "application/json",
    },
    signal: controller.signal,
    });

    clearTimeout(timeoutId);

    if (!response.ok) {
    return { sucesso: false, erro: `Erro HTTP ${response.status}` };
    }

    const resultado = await response.json();

    if (!resultado.success || !resultado.data) {
    return { sucesso: false, erro: "CPF nao encontrado" };
    }

    return {
    sucesso: true,
    dados: {
    nome: resultado.data.name,
    cpf: resultado.data.cpf,
    genero: resultado.data.gender,
    dataNascimento: resultado.data.birthDate,
    dia: resultado.data.day,
    mes: resultado.data.month,
    ano: resultado.data.year,
    },
    };
    } catch (error) {
    clearTimeout(timeoutId);

    if (error.name === "AbortError") {
    return { sucesso: false, erro: "Timeout na consulta" };
    }

    return { sucesso: false, erro: error.message };
    }
}

// Registrar handler IPC
ipcMain.handle("cpf:consultar", async (_event, cpf) => {
    return await consultarCPF(cpf);
});

function createWindow() {
    const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    preload: path.join(__dirname, "preload.js"),
    contextIsolation: true,
    nodeIntegration: false,
    sandbox: true,
    },
    });

    mainWindow.loadFile("index.html");
}

app.whenReady().then(createWindow);

app.on("window-all-closed", () => {
    if (process.platform !== "darwin") app.quit();
});

Preload script para IPC seguro

O preload script expoe uma API limitada ao renderer via contextBridge:

// preload.js
const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("cpfAPI", {
    consultar: (cpf) => ipcRenderer.invoke("cpf:consultar", cpf),
});

Isso cria um objeto window.cpfAPI no renderer com apenas o método consultar, sem expor nenhuma funcionalidade do Node.js.


Interface do renderer

O HTML e JavaScript do renderer utilizam a API exposta pelo preload:

<!-- index.html -->
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy"
    content="default-src 'self'; script-src 'self'">
    <title>Validacao de CPF</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <main>
    <h2>Consulta de CPF</h2>

    <form id="cpfForm">
    <label for="cpfInput">CPF:</label>
    <input
    type="text"
    id="cpfInput"
    placeholder="000.000.000-00"
    maxlength="14"
    required
    />
    <button type="submit" id="btnConsultar">Consultar</button>
    </form>

    <div id="loading" class="hidden">Consultando...</div>
    <div id="erro" class="hidden"></div>
    <div id="resultado" class="hidden"></div>
    </main>

    <script src="renderer.js"></script>
</body>
</html>
// renderer.js
const form = document.getElementById("cpfForm");
const cpfInput = document.getElementById("cpfInput");
const loadingEl = document.getElementById("loading");
const erroEl = document.getElementById("erro");
const resultadoEl = document.getElementById("resultado");
const btnConsultar = document.getElementById("btnConsultar");

function formatarCPF(valor) {
    const digitos = valor.replace(/\D/g, "").slice(0, 11);
    if (digitos.length <= 3) return digitos;
    if (digitos.length <= 6)
    return `${digitos.slice(0, 3)}.${digitos.slice(3)}`;
    if (digitos.length <= 9)
    return `${digitos.slice(0, 3)}.${digitos.slice(3, 6)}.${digitos.slice(6)}`;
    return `${digitos.slice(0, 3)}.${digitos.slice(3, 6)}.${digitos.slice(6, 9)}-${digitos.slice(9)}`;
}

cpfInput.addEventListener("input", (e) => {
    e.target.value = formatarCPF(e.target.value);
});

form.addEventListener("submit", async (e) => {
    e.preventDefault();

    const cpf = cpfInput.value;
    erroEl.classList.add("hidden");
    resultadoEl.classList.add("hidden");
    loadingEl.classList.remove("hidden");
    btnConsultar.disabled = true;

    try {
    // Chama o main process via IPC seguro
    const resultado = await window.cpfAPI.consultar(cpf);

    if (resultado.sucesso) {
    resultadoEl.innerHTML = `
    <h3>Dados encontrados</h3>
    <p><strong>Nome:</strong> ${resultado.dados.nome}</p>
    <p><strong>Genero:</strong> ${resultado.dados.genero === "M" ? "Masculino" : "Feminino"}</p>
    <p><strong>Nascimento:</strong> ${resultado.dados.dataNascimento}</p>
    `;
    resultadoEl.classList.remove("hidden");
    } else {
    erroEl.textContent = resultado.erro;
    erroEl.classList.remove("hidden");
    }
    } catch (error) {
    erroEl.textContent = "Erro inesperado na consulta.";
    erroEl.classList.remove("hidden");
    } finally {
    loadingEl.classList.add("hidden");
    btnConsultar.disabled = false;
    }
});

Armazenamento seguro da chave de API

Em aplicações desktop, a chave de API pode ser armazenada de forma segura usando o safeStorage do Electron:

// keystore.js
const { safeStorage } = require("electron");
const fs = require("fs");
const path = require("path");

const KEY_FILE = path.join(app.getPath("userData"), "api_key.enc");

function salvarChave(apiKey) {
    if (!safeStorage.isEncryptionAvailable()) {
    throw new Error("Criptografia nao disponivel neste sistema");
    }

    const encrypted = safeStorage.encryptString(apiKey);
    fs.writeFileSync(KEY_FILE, encrypted);
}

function recuperarChave() {
    if (!fs.existsSync(KEY_FILE)) return null;

    const encrypted = fs.readFileSync(KEY_FILE);
    return safeStorage.decryptString(encrypted);
}

module.exports = { salvarChave, recuperarChave };

O safeStorage utiliza o Keychain no macOS, o DPAPI no Windows e o Secret Service no Linux -- as APIs nativas de cada sistema operacional para armazenamento seguro.


Cache local com SQLite

Para evitar consultas repetidas e permitir uso offline parcial, utilize SQLite via better-sqlite3:

// cache.js
const Database = require("better-sqlite3");
const path = require("path");

const db = new Database(
    path.join(app.getPath("userData"), "cpf_cache.db")
);

db.exec(`
    CREATE TABLE IF NOT EXISTS cpf_cache (
    cpf TEXT PRIMARY KEY,
    dados TEXT NOT NULL,
    consultado_em TEXT NOT NULL,
    expira_em TEXT NOT NULL
    )
`);

const inserir = db.prepare(`
    INSERT OR REPLACE INTO cpf_cache (cpf, dados, consultado_em, expira_em)
    VALUES (?, ?, datetime('now'), datetime('now', '+24 hours'))
`);

const buscar = db.prepare(`
    SELECT dados FROM cpf_cache
    WHERE cpf = ? AND expira_em > datetime('now')
`);

function buscarCache(cpf) {
    const row = buscar.get(cpf);
    return row ? JSON.parse(row.dados) : null;
}

function salvarCache(cpf, dados) {
    inserir.run(cpf, JSON.stringify(dados));
}

module.exports = { buscarCache, salvarCache };

Content Security Policy

A CSP no header do HTML e essencial para proteger a aplicação Electron:

<meta http-equiv="Content-Security-Policy"
    content="default-src 'self'; script-src 'self'; connect-src 'self'">

Como as chamadas HTTP acontecem no main process (Node.js), o renderer não precisa de permissão para connect-src externo -- toda a comunicação passa pelo IPC.


Empacotamento e distribuição

Use o electron-builder para empacotar a aplicação:

{
    "build": {
    "appId": "com.minhaempresa.cpf-validator",
    "productName": "CPF Validator",
    "mac": { "target": "dmg" },
    "win": { "target": "nsis" },
    "linux": { "target": "AppImage" }
    }
}
npx electron-builder --mac --win --linux

Perguntas frequentes

Como a chave de API da CPFHub.io deve ser protegida em um app Electron?

A chave de API nunca deve ficar no renderer process nem em arquivos de configuração sem criptografia. O caminho correto é armazená-la no main process via safeStorage do Electron, que usa o Keychain no macOS, DPAPI no Windows e Secret Service no Linux. Alternativamente, carregue a chave por variável de ambiente no momento do empacotamento e acesse-a apenas no main.

A API CPFHub.io funciona para todos os volumes de consulta em apps desktop?

Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos internos menores. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.

Como garantir conformidade com a LGPD ao usar uma API de CPF em Electron?

Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário no banco de dados local e implemente controle de acesso aos logs de consulta. O cache SQLite local deve ter tempo de expiração curto e não deve persistir dados além do necessário para a operação da aplicação.

Qual a latência esperada nas consultas à CPFHub.io e como lidar com ela na interface?

A latência típica é de ~900ms. Para manter a interface responsiva, desabilite o botão de consulta durante a chamada, exiba um indicador de carregamento e configure um timeout de 10 segundos no AbortController — como mostrado nos exemplos de código acima. Consultas repetidas ao mesmo CPF devem ser atendidas pelo cache SQLite local sem latência.


Conclusão

Electron oferece uma plataforma solida para aplicações desktop que precisam validar CPF, com a seguranca do isolamento entre processos e o poder do Node.js para chamadas HTTP. A arquitetura com IPC garante que a chave de API da CPFHub.io

Cadastre-se em cpfhub.io

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

WhatsAppFale conosco via WhatsApp