---
title: "Autenticação com certificado em nuvem"
description: "Autentique usuários com o certificado em nuvem pelo CloudHub: o usuário assina um nonce no provedor e a sua biblioteca de assinatura confere a assinatura e valida o certificado."
canonical: "https://docs.lacunasoftware.com/articles/cloudhub/authentication"
locale: "pt-BR"
product: "CloudHub"
last_updated: "2026-10-12"
---

# Autenticação com certificado em nuvem

Um certificado em nuvem permite que o usuário entre na sua aplicação do mesmo jeito que um smart
card: o usuário prova que controla a chave do certificado assinando um desafio aleatório (um
**nonce**), e a sua biblioteca de assinatura confere a assinatura e valida o certificado. Com o
CloudHub, o usuário aprova essa assinatura no aplicativo do provedor, em vez de digitar um PIN em um
token.

O fluxo é o [fluxo de assinatura](https://docs.lacunasoftware.com/articles/cloudhub/how-it-works#o-fluxo-passo-a-passo) com um nonce no lugar do
documento:

1. Crie uma sessão `SingleSignature` e envie o usuário a um provedor.
2. Quando o usuário voltar, leia o certificado dele no CloudHub.
3. Peça à sua biblioteca de assinatura que crie um nonce e peça ao CloudHub que assine o hash dele.
4. Peça à sua biblioteca de assinatura que confira a assinatura e valide o certificado.
5. Encontre o usuário pelo CPF do certificado e autentique-o.

Use `SingleSignature` para isso, não `AuthenticationSession`: a maioria dos provedores não oferece
sessões de autenticação, e uma sessão que não assina não prova nada sobre a chave.

O certificado sozinho não é prova. É o passo 4 que mostra que o usuário controla a chave neste
momento e que o certificado é válido e confiável.

## O exemplo

Cada exemplo é o handler da URI de redirecionamento; o handler que cria a sessão é o mesmo de
[Primeiros passos](https://docs.lacunasoftware.com/articles/cloudhub/get-started#4-criar-uma-sessão), sem identificador se você preferir que o
usuário se identifique no provedor. Os exemplos seguem os do
[PKI Suite Samples](https://github.com/LacunaSoftware/PkiSuiteSamples):

| Linguagem | Biblioteca de assinatura |
|---|---|
| C# | PKI SDK (`PKCertificateAuthentication`) |
| PHP | PKI Express (`Authentication`) |
| Python | PKI Express (`Authentication`) |
| Node.js | Rest PKI (`Authentication`) |
| Java | PKI Express (`Authentication`) |

O PKI SDK e o Rest PKI fornecem o hash a assinar. O PKI Express fornece o próprio nonce, então esses
exemplos calculam o hash dele com o algoritmo que o PKI Express indica.

**C#**

```csharp title="Controllers/CloudLoginController.cs"
using Lacuna.Cloudhub.Api;
using Lacuna.Cloudhub.Client;
using Lacuna.Pki;
using Lacuna.Pki.Stores;
using Microsoft.AspNetCore.Mvc;

public class CloudLoginController : Controller {

    private static readonly CloudhubClient cloudhub = new CloudhubClient(
        "https://cloudhub.lacunasoftware.com/",
        Environment.GetEnvironmentVariable("CLOUDHUB_API_KEY"));

    // GET /CloudLogin/Complete?session=…: the redirect URI
    public async Task<IActionResult> Complete(string session) {
        if (string.IsNullOrEmpty(session)) {
            return RedirectToAction("Index"); // not authorized
        }

        // 1. The user's certificate, from CloudHub
        byte[] certificate = await cloudhub.GetCertificateAsync(session);

        // 2. PKI SDK creates a nonce. Store nonces in your database in production.
        var auth = new PKCertificateAuthentication(new FileSystemNonceStore(nonceFolder));
        byte[] nonce = auth.Start();

        // 3. CloudHub signs the nonce's hash with the user's key
        var signature = await cloudhub.SignHashAsync(new SignHashRequest {
            Session = session,
            Hash = PKCertificateAuthentication.DigestAlgorithm.ComputeHash(nonce),
            DigestAlgorithmOid = PKCertificateAuthentication.DigestAlgorithm.Oid,
        });

        // 4. PKI SDK checks the signature and validates the certificate
        var validationResults = auth.Complete(nonce, certificate, signature,
            TrustArbitrators.PkiBrazil, out PKCertificate userCert);
        if (!validationResults.IsValid) {
            return View("Failed", validationResults);
        }

        // 5. Find the user by userCert.PkiBrazil.CPF and sign them in
        return RedirectToAction("Index", "Home");
    }
}
```

**PHP**

```php title="complete.php"
<?php
require __DIR__ . '/../vendor/autoload.php';

use Lacuna\Cloudhub\CloudhubClient;
use Lacuna\Cloudhub\SignHashRequest;
use Lacuna\PkiExpress\Authentication;

$cloudhub = new CloudhubClient('https://cloudhub.lacunasoftware.com/', getenv('CLOUDHUB_API_KEY'));

$session = $_GET['session'] ?? null;
if (empty($session)) {
    header('Location: /login.php'); // not authorized
    exit;
}

// 1. The user's certificate, from CloudHub (base64)
$certificate = $cloudhub->getCertificateAsync($session);

// 2. PKI Express creates a nonce, base64-encoded
$auth = new Authentication();
$start = $auth->start();

// 3. CloudHub signs the nonce's hash with the user's key
$phpHashNames = [
    '2.16.840.1.101.3.4.2.1' => 'sha256',
    '2.16.840.1.101.3.4.2.2' => 'sha384',
    '2.16.840.1.101.3.4.2.3' => 'sha512',
];
$hash = hash($phpHashNames[$start->digestAlgorithmOid], base64_decode($start->nonce), true);
$signature = $cloudhub->signHashAsync(new SignHashRequest(
    $session,
    base64_encode($hash),
    null,
    $start->digestAlgorithmOid
));

// 4. PKI Express checks the signature and validates the certificate
$auth->setNonce($start->nonce);
$auth->setCertificateBase64($certificate);
$auth->setSignature($signature);
$result = $auth->complete();
if (!$result->validationResults->isValid()) {
    http_response_code(403);
    echo nl2br(htmlspecialchars((string) $result->validationResults));
    exit;
}

// 5. Find the user by $result->certificate->pkiBrazil->cpf and sign them in
```

**Python**

```python title="cloud_login.py"
import base64
import hashlib

from flask import Blueprint, abort, redirect, request, url_for
from pkiexpress import Authentication

from cloudhub import call_cloudhub  # the helper from "Get started"

blueprint = Blueprint('cloud_login', __name__, url_prefix='/cloud-login')

HASH_FUNCTIONS = {
    '2.16.840.1.101.3.4.2.1': hashlib.sha256,
    '2.16.840.1.101.3.4.2.2': hashlib.sha384,
    '2.16.840.1.101.3.4.2.3': hashlib.sha512,
}


@blueprint.route('/complete')
def complete():
    cloudhub_session = request.args.get('session')
    if not cloudhub_session:
        return redirect(url_for('.index'))  # not authorized

    # 1. The user's certificate, from CloudHub (base64)
    certificate = call_cloudhub('GET', '/api/sessions/certificate',
                                params={'session': cloudhub_session})

    # 2. PKI Express creates a nonce, base64-encoded
    auth = Authentication()
    start = auth.start()

    # 3. CloudHub signs the nonce's hash with the user's key
    digest = HASH_FUNCTIONS[start.digest_algorithm_oid](base64.b64decode(start.nonce)).digest()
    signature = call_cloudhub('POST', '/api/sessions/sign-hash', json={
        'session': cloudhub_session,
        'hash': base64.b64encode(digest).decode('ascii'),
        'digestAlgorithmOid': start.digest_algorithm_oid,
    })

    # 4. PKI Express checks the signature and validates the certificate
    auth.nonce_base64 = start.nonce
    auth.certificate_base64 = certificate
    auth.signature_base64 = signature
    result = auth.complete()
    if not result.validation_results.is_valid:
        abort(403, str(result.validation_results))

    # 5. Find the user by result.certificate.pki_brazil.cpf and sign them in
    return redirect(url_for('home'))
```

**Node.js**

```js title="routes/cloud-login.js"
const express = require('express');
const { CloudHubClient } = require('cloudhub-client');
const {
  Authentication,
  CompleteAuth,
  RestPkiClient,
  StandardSecurityContexts,
} = require('restpki-client');

const cloudhub = new CloudHubClient('https://cloudhub.lacunasoftware.com/', process.env.CLOUDHUB_API_KEY);
const restPki = new RestPkiClient('https://pki.rest/', process.env.RESTPKI_ACCESS_TOKEN);
const router = express.Router();

// GET /cloud-login/complete?session=…: the redirect URI
router.get('/complete', async (req, res, next) => {
  try {
    const { session } = req.query;
    if (!session) {
      return res.redirect('/cloud-login'); // not authorized
    }

    // 1. The user's certificate, from CloudHub (base64)
    const certificate = await cloudhub.getCertificateAsync(session);

    // 2. Rest PKI starts the authentication and returns the hash to sign
    const auth = new Authentication(restPki);
    const start = await auth.start(StandardSecurityContexts.PKI_BRAZIL);

    // 3. CloudHub signs it with the user's key
    const signature = await cloudhub.signHashAsync({
      session,
      hash: start.toSignHash,
      digestAlgorithmOid: start.digestAlgorithmOid,
    });

    // 4. Rest PKI checks the signature and validates the certificate
    const result = await auth.complete(start.token, new CompleteAuth({ signature, certificate }));
    if (!result.validationResults.isValid()) {
      return res.status(403).send(result.validationResults.toString());
    }

    // 5. Find the user by result.certificate.pkiBrazil.cpf and sign them in
    res.redirect('/');
  } catch (err) {
    next(err);
  }
});

module.exports = router;
```

**Java**

```java title="CloudLoginController.java"
import cloudhub.SessionsApi;
import cloudhub.client.CloudhubClient;
import cloudhub.client.model.SignHashRequest;
import com.lacunasoftware.pkiexpress.AuthCompleteResult;
import com.lacunasoftware.pkiexpress.AuthStartResult;
import com.lacunasoftware.pkiexpress.Authentication;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/cloud-login")
public class CloudLoginController {

    private static final Map<String, String> HASH_NAMES = new HashMap<>();
    static {
        HASH_NAMES.put("2.16.840.1.101.3.4.2.1", "SHA-256");
        HASH_NAMES.put("2.16.840.1.101.3.4.2.2", "SHA-384");
        HASH_NAMES.put("2.16.840.1.101.3.4.2.3", "SHA-512");
    }

    private final SessionsApi cloudhub = new SessionsApi(
        new CloudhubClient("https://cloudhub.lacunasoftware.com", System.getenv("CLOUDHUB_API_KEY")));

    // GET /cloud-login/complete?session=…: the redirect URI
    @GetMapping("/complete")
    public String complete(@RequestParam(required = false) String session) throws Exception {
        if (session == null || session.isEmpty()) {
            return "redirect:/cloud-login"; // not authorized
        }

        // 1. The user's certificate, from CloudHub (base64)
        String certificate = unquote(cloudhub.apiSessionsCertificateGet(session));

        // 2. PKI Express creates a nonce, base64-encoded
        Authentication auth = new Authentication();
        AuthStartResult start = auth.start();

        // 3. CloudHub signs the nonce's hash with the user's key
        byte[] nonce = Base64.getDecoder().decode(start.getNonce());
        byte[] hash = MessageDigest.getInstance(HASH_NAMES.get(start.getDigestAlgorithmOid())).digest(nonce);
        SignHashRequest signRequest = new SignHashRequest();
        signRequest.setSession(session);
        signRequest.setHash(hash);
        signRequest.setDigestAlgorithmOid(start.getDigestAlgorithmOid());
        String signature = unquote(cloudhub.apiSessionsSignHashPost(signRequest));

        // 4. PKI Express checks the signature and validates the certificate
        auth.setNonce(start.getNonce());
        auth.setCertificateBase64(certificate);
        auth.setSignature(signature);
        AuthCompleteResult result = auth.complete();
        if (!result.getValidationResults().isValid()) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, result.getValidationResults().toString());
        }

        // 5. Find the user by result.getCertificate().getPkiBrazil().getCpf() and sign them in
        return "redirect:/";
    }

    // The certificate and sign-hash operations answer with a JSON string ("MIIG..."),
    // and the client hands back its raw bytes, quotes included
    private static String unquote(byte[] body) {
        return new String(body, StandardCharsets.UTF_8).replace("\"", "");
    }
}
```

## Associando o certificado a um usuário

O certificado validado identifica uma pessoa, não uma conta na sua aplicação. Faça a associação como
o seu negócio exigir; com certificados ICP-Brasil, as chaves usuais são o CPF (e-CPF) ou o CNPJ e o
CPF do responsável (e-CNPJ). Se você criou a sessão com um identificador, confira também se o
certificado traz esse identificador: o usuário pode ter entrado no provedor com outra conta.

**Demonstrações e exemplos**

- Authenticate with a cloud certificate using CloudHub: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/AuthenticationCloudHubSdkController.cs) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-cloudhub.js)
