Autenticação com certificado digital ICP-Brasil no seu sistema web. Popup com ticket de uso único, verificação no seu backend e retorno em JSON pronto para o seu login.
// inclua ihubauth.js e chame:
ihubauth(TOKEN, function (erro, resultado) {
if (erro) return tratarErro(erro);
// envie resultado.ticket ao seu backend → /verificar
enviarAoBackend(resultado.ticket);
});
Antes de ir para produção, você pode validar a integração em ambiente de desenvolvimento ou homologação na sua rede local.
Autenticações originadas de redes locais não descontam créditos do seu plano — valide a integração à vontade, sem afetar seu saldo. Valem as faixas privadas padrão:
Para evitar abuso, cada ambiente de homologação em rede local é limitado a 7 autenticações por dia — suficiente para validar a integração ponta a ponta.
Você inclui a integração na sua página de login e configura o token de licença. O serviço valida a cadeia ICP-Brasil e devolve ao navegador apenas um ticket de uso único; o seu backend troca esse ticket pela identidade do titular — que nunca passa pelo navegador.
A autenticação acontece em um popup: a janela devolve ao navegador apenas um ticket de uso único, e o seu backend troca esse ticket pela identidade do titular. Os dados do certificado nunca passam pelo navegador.
Inclua o arquivo ihubauth.js (fornecido pela iHub) e chame ihubauth(TOKEN, callback). No sucesso, o callback recebe um ticket de uso único — não os dados. Envie esse ticket ao seu backend, que o troca pela identidade em /certificado/verificar.
<!-- 1. Inclua a biblioteca fornecida pela iHub -->
<script src="/js/ihubauth.js"></script>
<!-- 2. Botão de login -->
<button onclick="autenticar()">Autenticar com certificado digital</button>
<script>
// Cole aqui o token de licença fornecido pela iHub:
var TOKEN = 'SEU_TOKEN_AQUI';
function autenticar() {
ihubauth(TOKEN, function (erro, resultado) {
if (erro) {
// erro = { codigo, etapa, mensagem } — ver "Códigos de erro"
alert(erro.mensagem);
return;
}
// resultado.ticket — opaco, uso único (~120s). Envie ao SEU backend:
fetch('/sessao/certificado', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ticket: resultado.ticket })
}).then(function (r) {
if (r.ok) location.href = '/painel'; // sessão criada no backend
else alert('Falha ao validar o login.');
});
});
}
</script>
A biblioteca já aponta para a API de produção. Se precisar, dá para informar a base por opção: ihubauth(TOKEN, callback, { baseUrl: 'https://api.ihub.com.br' }). Para o Popup funcionar, o navegador precisa permitir popups para o domínio da sua aplicação.
O frontend recebe um ticket; o seu backend o troca pela identidade autoritativa do titular. Assim a identidade nunca é confiada a partir do navegador — só o ticket, opaco e de uso único, trafega no cliente.
Do seu backend, faça um POST para o endpoint abaixo com o token de licença e o ticket recebido. A resposta traz os dados do titular (mesmo objeto da seção Retorno).
POST https://api.ihub.com.br/certificado/verificar
Content-Type: application/json
{ "token": "SEU_TOKEN_AQUI", "ticket": "<ticket recebido do frontend>" }
→ 200 OK
{
"Nome": "JOAO DA SILVA",
"CPF": "12345678909",
"Validade": "22/07/2026",
"DiasValidade": 425,
"Verificado": true,
"TipoCertificado": "A1"
}
<?php
// Endpoint do SEU backend que recebe o ticket do frontend.
// Ex.: POST /sessao/certificado · corpo JSON: { "ticket": "..." }
const IHUB_TOKEN = 'SEU_TOKEN_AQUI'; // token de licença (config no servidor)
const IHUB_VERIFICAR = 'https://api.ihub.com.br/certificado/verificar';
session_start();
// 1) Proteja contra CSRF como qualquer POST que cria sessão
// (token anti-CSRF do seu framework, ou um nonce/state ligado à sessão).
$entrada = json_decode(file_get_contents('php://input'), true);
$ticket = $entrada['ticket'] ?? null;
if (!$ticket) {
http_response_code(400);
exit(json_encode(['erro' => 'ticket ausente']));
}
// 2) Resgata o ticket na iHub (servidor-a-servidor)
$ch = curl_init(IHUB_VERIFICAR);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['token' => IHUB_TOKEN, 'ticket' => $ticket]),
CURLOPT_TIMEOUT => 10,
]);
$resposta = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
http_response_code(401);
exit(json_encode(['erro' => 'ticket inválido ou expirado']));
}
$dados = json_decode($resposta, true);
// 3) Confere a validação da cadeia ICP-Brasil
if (($dados['Verificado'] ?? false) !== true) {
http_response_code(401);
exit(json_encode(['erro' => 'certificado não verificado']));
}
// 4) Localize o usuário pelo CPF/CNPJ na SUA base (find-or-create)
$usuario = encontrarUsuarioPorDocumento($dados['CPF']); // implemente na sua app
if (!$usuario) {
http_response_code(403);
exit(json_encode(['erro' => 'usuário não cadastrado']));
}
// 5) Cria a sessão com os dados da iHub (NUNCA com algo vindo do frontend)
$_SESSION['usuario_id'] = $usuario['id'];
$_SESSION['nome'] = $dados['Nome'];
echo json_encode(['ok' => true]);
import requests
from flask import Flask, request, session, jsonify
app = Flask(__name__)
app.secret_key = "..." # chave de sessão do Flask
IHUB_TOKEN = "SEU_TOKEN_AQUI" # token de licença (config no servidor)
IHUB_VERIFICAR = "https://api.ihub.com.br/certificado/verificar"
@app.post("/sessao/certificado")
def sessao_certificado():
# 1) Proteja contra CSRF como qualquer POST que cria sessão
# (Flask-WTF/CSRF, ou um nonce/state ligado à sessão).
ticket = (request.get_json(silent=True) or {}).get("ticket")
if not ticket:
return jsonify(erro="ticket ausente"), 400
# 2) Resgata o ticket na iHub (servidor-a-servidor)
r = requests.post(
IHUB_VERIFICAR,
json={"token": IHUB_TOKEN, "ticket": ticket},
timeout=10,
)
if r.status_code != 200:
return jsonify(erro="ticket inválido ou expirado"), 401
dados = r.json()
# 3) Confere a validação da cadeia ICP-Brasil
if dados.get("Verificado") is not True:
return jsonify(erro="certificado não verificado"), 401
# 4) Localize o usuário pelo CPF/CNPJ na SUA base (find-or-create)
usuario = encontrar_usuario_por_documento(dados["CPF"]) # implemente na sua app
if not usuario:
return jsonify(erro="usuário não cadastrado"), 403
# 5) Cria a sessão com os dados da iHub (NUNCA com algo vindo do frontend)
session["usuario_id"] = usuario["id"]
session["nome"] = dados["Nome"]
return jsonify(ok=True)
Um cliente reutilizável que encapsula o resgate do ticket — use-o em qualquer app .NET.
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
// Cliente reutilizável que troca o ticket pela identidade na iHub.
public sealed class IHubAuthClient
{
private const string Verificar = "https://api.ihub.com.br/certificado/verificar";
private readonly HttpClient _http;
private readonly string _token; // token de licença (config no servidor)
public IHubAuthClient(HttpClient http, string token)
{
_http = http;
_token = token;
}
// Resgata o ticket. Lança se inválido/expirado ou não verificado.
public async Task<CertificadoDados> VerifyAsync(string ticket, CancellationToken ct = default)
{
var resp = await _http.PostAsJsonAsync(
Verificar, new { token = _token, ticket }, ct);
if (!resp.IsSuccessStatusCode)
throw new InvalidOperationException("Ticket inválido ou expirado.");
var dados = await resp.Content.ReadFromJsonAsync<CertificadoDados>(cancellationToken: ct)
?? throw new InvalidOperationException("Resposta vazia.");
if (!dados.Verificado)
throw new InvalidOperationException("Certificado não verificado.");
return dados;
}
}
public sealed record CertificadoDados(
[property: JsonPropertyName("Nome")] string Nome,
[property: JsonPropertyName("CPF")] string Cpf,
[property: JsonPropertyName("Validade")] string Validade,
[property: JsonPropertyName("DiasValidade")] int DiasValidade,
[property: JsonPropertyName("Verificado")] bool Verificado,
[property: JsonPropertyName("TipoCertificado")] string TipoCertificado);
O controller que recebe o ticket do frontend, usa o cliente acima e cria a sessão por cookie.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
[ApiController]
[Route("sessao")]
public class SessaoController : ControllerBase
{
private readonly IHubAuthClient _ihub;
private readonly IUsuarioRepo _usuarios;
public SessaoController(IHubAuthClient ihub, IUsuarioRepo usuarios)
{
_ihub = ihub;
_usuarios = usuarios;
}
public record Entrada(string Ticket);
[HttpPost("certificado")]
[ValidateAntiForgeryToken] // protege o POST de login contra CSRF
public async Task<IActionResult> Certificado([FromBody] Entrada entrada)
{
if (string.IsNullOrEmpty(entrada.Ticket))
return BadRequest(new { erro = "ticket ausente" });
// 1) Resgata o ticket na iHub (servidor-a-servidor)
CertificadoDados dados;
try { dados = await _ihub.VerifyAsync(entrada.Ticket); }
catch { return Unauthorized(new { erro = "ticket inválido ou expirado" }); }
// 2) Localize o usuário pelo CPF/CNPJ na SUA base
var usuario = await _usuarios.PorDocumentoAsync(dados.Cpf);
if (usuario is null)
return StatusCode(403, new { erro = "usuário não cadastrado" });
// 3) Cria a sessão (cookie) com os dados da iHub
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, usuario.Id.ToString()),
new(ClaimTypes.Name, dados.Nome),
};
var id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id));
return Ok(new { ok = true });
}
}
O /certificado/verificar devolve ao seu backend o objeto com os dados do titular.
{
"Nome": "JOAO DA SILVA",
"CPF": "12345678909", // CPF (11 díg) ou CNPJ (14 díg)
"Validade": "22/07/2026",
"DiasValidade": 425,
"Verificado": true,
"TipoCertificado": "A1" // "A1" ou "A3"
}
O popup de autenticação conta com vários níveis de detecção de robôs e automações não autorizadas. Tudo acontece do lado do IHub-Auth: nada muda no seu código, e o usuário legítimo não ganha nenhuma etapa a mais.
Além da proteção de borda da Cloudflare e do desafio de verificação que o usuário já vê, toda abertura do popup passa por níveis adicionais de detecção de automação, que avaliam cada tentativa antes de liberar o login no sistema.
Não publicamos quais são nem como funcionam, e eles são revistos periodicamente. Descrever o critério em público facilitaria contorná-lo, e isso esvaziaria a proteção do seu sistema.
O que interessa para a sua integração:
A recusa acontece dentro do popup, antes de qualquer consumo: nenhum crédito é debitado e nenhum ticket é emitido. O popup exibe uma mensagem com um código de referência de 8 caracteres e não se fecha sozinho. Ao fechar, o seu sistema recebe o erro pelo caminho normal do callback.
A mensagem exibida é a mesma para qualquer nível acionado, de propósito: ela não revela qual critério barrou o acesso. O motivo fica no nosso registro e é localizado pelo código de referência.
Integração máquina a máquina — rotinas agendadas, coletas periódicas, sistemas de terceiros que consultam em lote — deve usar o webservice do sistema, não a tela de login feita para pessoas. É mais rápido, não depende de navegador e não disputa capacidade com o usuário final.
Se uma automação precisar continuar passando pelo popup por um período determinado, é possível cadastrar uma exceção com prazo de validade para endereços fixos ou para um certificado específico.
A decisão é sua, não nossa. O integrador barrado procura o suporte do seu sistema com o código de referência; cabe a você, responsável pela licença, avaliar se aquele uso é autorizado. Confirmado, você nos encaminha o pedido com o código e nós cadastramos a exceção. Não liberamos ninguém por conta própria nem atendemos o integrador diretamente.
Trate cada código com uma mensagem clara para o usuário. O callback recebe { codigo, etapa, mensagem } como primeiro parâmetro.
ihubauth(TOKEN, function (erro, resultado) {
if (erro) {
switch (erro.codigo) {
case 456:
alert('Seu certificado foi revogado. Renove-o na sua certificadora.');
break;
case 457:
alert('Certificado não encontrado. Verifique se está instalado.');
break;
default:
alert(erro.mensagem);
}
return;
}
enviarAoBackend(resultado.ticket); // envie o ticket ao seu backend
});