---
title: "Certificate authentication - PKI Express"
description: "Certificate authentication with PKI Express and Web PKI: PKI Express issues a nonce, Web PKI signs it with the user's certificate, and PKI Express validates the signature and the certificate."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-express/authentication"
locale: "en"
product: "PKI Express"
last_updated: "2026-10-12"
---

# Certificate authentication - PKI Express

Users sign in to your application with their digital certificate: [PKI Express](https://docs.lacunasoftware.com/en-us/articles/pki-express) issues a nonce (a random value), [Web PKI](https://docs.lacunasoftware.com/en-us/articles/web-pki)
signs it with the user's certificate in the browser, and PKI Express validates the signature and the certificate. 
PKI Express added this in 1.4.0, with the `start-auth` and `complete-auth` commands. 

The code is Java, from the [PKI Suite samples](https://github.com/LacunaSoftware/PkiSuiteSamples), with `com.lacunasoftware.pkiexpress:pki-express`
(1.20.0); the browser script is the same in the Node.js sample. 
For the method itself, see [Certificate authentication](https://docs.lacunasoftware.com/en-us/articles/pki-guide/cert-auth) in the PKI guide.

## Flow

1. **Server**: `start()` returns the nonce and the digest algorithm; the page receives both in hidden fields.
1. **Browser**: Web PKI lists the user's certificates, reads the one they pick and signs the nonce with `signData()`; the script fills in the
   certificate and the signature and submits the form.
1. **Server**: `complete()` with the nonce, the certificate and the signature returns the validation results and the certificate.
1. **Server**: if the results are valid, find your user by a field of the certificate and sign them in.

## 1. Start the authentication

```java
Authentication auth = new Authentication();
Util.setPkiDefaults(auth);
AuthStartResult result = auth.start();
Util.setNoCacheHeaders(response);
model.addAttribute("nonce", result.getNonce());
model.addAttribute("digestAlgorithm", result.getDigestAlgorithm());
```

* The nonce works for a single authentication attempt. The sample sends headers that keep the browser from caching the page
  (`Cache-Control: no-cache, no-store, must-revalidate`, `Pragma: no-cache`, `Expires: -1`), so that the back button doesn't show an old
  nonce. 
* `Util.setPkiDefaults()` applies the trusted roots, which decide which certificates are accepted (see
  [Which certificates are trusted](https://docs.lacunasoftware.com/en-us/articles/pki-express/validate-signatures#trust)). 
* PKI Express keeps the nonces it issued in a nonce store, the `transferDataFolder` (by default the temporary folder), to check them on
  `complete()`. To keep the nonces in your own store and check them yourself, turn on external storage. 
  The Python library (1.10.4) inverts this option: it passes the nonce store only when external storage is on. 
  TODO(docs): whether PKI Express checks nonce reuse when no nonce store is given.

## 2. Sign the nonce in the browser

The page puts `nonce` and `digestAlgorithm` in hidden fields, with fields for the certificate (`certContent`) and the signature
(`signature`). 
When the user signs in, the script reads the chosen certificate, signs the nonce with `signData()` and submits the form: 

```js
var selectedCertThumbprint = formElements.certificateSelect.val();
pki.readCertificate(selectedCertThumbprint).success(function (certEncoded) {
	pki.signData({
		thumbprint: formElements.certificateSelect.val(),
		data: formElements.nonceField.val(),
		digestAlgorithm: formElements.digestAlgorithmField.val()
	}).success(function (signature) {
		formElements.signatureField.val(signature);
		formElements.certContentField.val(certEncoded);
		formElements.form.submit();
	});
});
```

* The nonce is the data to sign, not a hash, so the script uses `signData`, not `signHash`. See
  [the difference between signData and signHash](https://docs.lacunasoftware.com/en-us/articles/web-pki/faq/sign-data-vs-hash). 
* Before this, the script initializes Web PKI with `pki.init()` and lists the certificates with `listCertificates()`, marking expired ones. 

## 3. Complete the authentication

```java
Authentication auth = new Authentication();
Util.setPkiDefaults(auth);
auth.setNonce(nonce);
auth.setCertificateBase64(certContent);
auth.setSignature(signature);
AuthCompleteResult result = auth.complete();

ValidationResults vr = result.getValidationResults();
if (!vr.isValid()) {
	String vrHtml = vr.toString()
		.replaceAll("\n", "<br>")
		.replaceAll("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
	model.addAttribute("vrHtml", vrHtml);
	return "authentication-express/failed";
}

model.addAttribute("userCert", result.getCertificate());
```

* Apply the same trust settings as at the start: the sample calls `Util.setPkiDefaults()` in both steps. 
* The other libraries name the inputs differently: Node.js `setNonce`, `setCertificateFromBase64` (returns a Promise) and `setSignature`;
  PHP `setNonce`, `setCertificateBase64` and `setSignature`; Python the properties `nonce_base64`, `certificate_base64` and
  `signature_base64`. 
* An unknown nonce fails with return code 28 (`NonceNotFound`), and a bad signature with 08 (`BadSignature`). See [Return codes](https://docs.lacunasoftware.com/en-us/articles/pki-express/return-codes). 

## 4. Sign the user in

When the results are valid, look up your user by one of the certificate's fields, such as `getEmailAddress()` or, for ICP-Brasil
certificates, `getPkiBrazil().getCpf()`; which field depends on your application. 
The sample's success page shows the subject's common name, the email, the ICP-Brasil certificate type, CPF, responsável, company, CNPJ, RG and
OAB. 

## See also

* [Validate signatures](https://docs.lacunasoftware.com/en-us/articles/pki-express/validate-signatures)
* [Certificate authentication](https://docs.lacunasoftware.com/en-us/articles/pki-guide/cert-auth) in the PKI guide
* [PKI Express quick reference](https://docs.lacunasoftware.com/en-us/articles/pki-express/quick-reference)

**Demos and samples**

- Live demo: [Your certificates in the browser](https://try.lacunasoftware.com/en/web-pki/certificates-in-the-browser)
- Authenticate with a digital certificate using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/AuthenticationExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/authentication-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/authentication-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/authentication_express.py)
