Set up service user multi-factor authentication

Add multi-factor authentication to an existing service user.

A client authentication certificate enables multi-factor authentication (mTLS) for DigiCert® ONE APIs. This certificate works alongside or replaces your API key to provide enhanced security for sensitive operations, such as code signing, certificate management, and audit log access.

In this tutorial, you will:

  • Create a CSR and request a signed client authentication certificate.
  • Generate a PKCS#12 file with your certificate and private key.
  • Verify mTLS authentication works with DigiCert® ONE APIs.
  • Manage certificate access and learn about rotation and expiration.

Use mTLS (client certificate) for:

  • All DigiCert® Software Trust Manager signing operations (required)
  • Production certificate issuance in DigiCert® Device Trust Manager (required)
  • Document signing operations in DigiCert® Document Trust Manager (required)
  • High-security operations in DigiCert® Account Manager (optional but recommended)

Use API key authentication for:

  • Read-only operations (listing resources, viewing details)
  • Development and testing workflows
  • Non-signing certificate management tasks

Why mTLS for sensitive operations:

  • Provides cryptographic proof of identity (something you have)
  • More difficult to compromise than API keys
  • Certificates can be quickly disabled without regenerating API keys
  • Meets compliance requirements for code signing

Before you begin

A DigiCert® ONE account.
Service user API token.
OpenSSL installed. Use openssl version to verify.
If using curl, jq installed. Use jq --version to verify.

Endpoint overview

PathMethodDescription
/account/api/v1/client-auth-certificatePOSTRequest a client authentication certificate for the authenticated user
/account/api/v1/user/meGETGet details about the authenticated user
/account/api/v1/client-auth-certificate/CERT_ID/enablePUTEnable or disable a client authentication certificate

Step 1: Generate a certificate signing request (CSR)

First you need to generate a private key and CSR using OpenSSL.

# Create directory for secure storage
mkdir -p ~/.digicert/certs
cd ~/.digicert/certs

# Generate private key and CSR
openssl req -new -newkey rsa:2048 -nodes \
  -keyout client_auth_private.key \
  -out client_auth.csr \
  -subj "/CN=Demo API Service User/O=Your Organization/C=US"

Immediately secure your client_auth_private.key file.

# Restrict access to owner read-only
chmod 400 client_auth_private.key

Two certificate files are created:

  • client_auth_private.key: Your private key. Keep this secure! You will safely store this in a PKCS#12 file in step 4.
  • client_auth.csr: Certificate signing request to send to DigiCert®.

Subject fields explained:

  • CN (Common Name): Use a descriptive name identifying the service (for example, “Production API Service”, “CI/CD Signing Service”, “IoT Provisioning Service”)
  • O (Organization): Your organization’s legal name
  • C (Country): Two-letter country code (US, CA, GB, DE)
  • Additional fields (OU, L, ST) are optional for client authentication certificates

Step 2: Request a client authentication certificate

Next you will submit your generated CSR to Account Manager /account/api/v1/client-auth-certificate endpoint and save the response for the next step.

Include these fields in your request body:

  • csr Certificate signing request content
  • name Friendly name for the client authentication certificate
  • exp (optional) Service user expiration date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)
jq -n \
  --arg csr "$(cat client_auth.csr)" \
  --arg name "API Client Auth Certificate" \
  --arg exp "2026-10-20T23:59:59Z" \
  '{csr: $csr, name: $name, expiration_date: $exp}' | \
curl -X POST https://demo.one.digicert.com/account/api/v1/client-auth-certificate \
  -H "x-api-key: SERVICE_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d @- | tee response.json | jq '.'

**What this does**:

- `jq` reads the CSR file and formats it as JSON
- `|` Pipes the JSON to curl for the API request
- `tee response.json` saves the response to a file
- `jq '.'` displays the formatted response
import requests

# Configuration
BASE_URL = "https://demo.one.digicert.com"
USER_API_TOKEN = "SERVICE_USER_TOKEN"
CSR_FILE = "client_auth.csr"
CERT_NAME = "API Client Auth Certificate"
EXPIRATION_DATE = "2026-10-20T23:59:59Z"

# Read CSR from file
with open(CSR_FILE, "r") as f:
    csr_content = f.read()

# Create client authentication certificate
payload = {
    "csr": csr_content,
    "name": CERT_NAME,
    "expiration_date": EXPIRATION_DATE
}

response = requests.post(
    f"{BASE_URL}/account/api/v1/client-auth-certificate",
    headers={
        "x-api-key": SERVICE_USER_TOKEN,
        "Content-Type": "application/json"
    },
    json=payload
)

print(f"Status Code: {response.status_code}")
print(response.json())
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class Example {
    public static void main(String[] args) throws Exception {
        // Configuration
        String baseUrl = "https://demo.one.digicert.com";
        String userApiToken = "SERVICE_USER_TOKEN";
        String csrFile = "client_auth.csr";
        String certName = "API Client Auth Certificate";
        String expirationDate = "2026-10-20T23:59:59Z";
        
        // Read CSR from file
        String csrContent = Files.readString(Paths.get(csrFile));
        
        // Build JSON payload
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode payload = mapper.createObjectNode();
        payload.put("csr", csrContent);
        payload.put("name", certName);
        payload.put("expiration_date", expirationDate);
        
        // Create client authentication certificate
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + "/account/api/v1/client-auth-certificate"))
            .header("x-api-key", userApiToken)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(payload)))
            .build();
        
        HttpResponse<String> response = client.send(
            request, 
            HttpResponse.BodyHandlers.ofString()
        );
        
        System.out.println("Status Code: " + response.statusCode());
        System.out.println(response.body());
    }
}
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Configuration
        string baseUrl = "https://demo.one.digicert.com";
        string userApiToken = "SERVICE_USER_TOKEN";
        string csrFile = "client_auth.csr";
        string certName = "API Client Auth Certificate";
        string expirationDate = "2026-10-20T23:59:59Z";
        
        // Read CSR from file
        string csrContent = await File.ReadAllTextAsync(csrFile);
        
        // Build JSON payload
        var payload = new JsonObject
        {
            ["csr"] = csrContent,
            ["name"] = certName,
            ["expiration_date"] = expirationDate
        };
        
        // Create client authentication certificate
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("x-api-key", userApiToken);
        
        var content = new StringContent(
            payload.ToJsonString(), 
            Encoding.UTF8, 
            "application/json"
        );
        
        var response = await client.PostAsync(
            $"{baseUrl}/account/api/v1/client-auth-certificate",
            content
        );
        
        string responseBody = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Status Code: {(int)response.StatusCode}");
        Console.WriteLine(responseBody);
    }
}

Successful response (HTTP 201):

{
  "id": "e281fe3b-fb97-4183-a4fa-205913a7aee4",
  "user_id": "8b210e38-b8a9-41df-a96e-1ffe4512f6d1",
  "name": "API Client Auth Certificate",
  "common_name": "Service User Client Auth",
  "x509_cert": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIQZA...\n-----END CERTIFICATE-----\n",
  "ca_cert": "-----BEGIN CERTIFICATE-----\nMIIEFjCCAv6gAwIBAgIQDx...\n-----END CERTIFICATE-----\n",
  "end_date": "2026-10-20T23:59:59Z",
  "start_date": "2025-10-20T00:00:00Z",
  "enabled": true,
  "serial_number": "6403F2A8B1234567890ABCDEF",
  "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
}

Step 3: Extract the certificate from response.json

With the response saved to response.json, you can extract your signed certificate in the x509_cert field.

jq -r '.x509_cert' response.json > client_auth.crt

Make sure the certificate was extracted:

openssl x509 -in client_auth.crt -text -noout | head -n 10

You should see certificate details including the subject, issuer, and validity dates.

Step 4: Create PKCS#12 file

Combine your private key and the signed certificate into a password-protected PKCS#12 file.

openssl pkcs12 -export \
  -out client_auth.p12 \
  -inkey client_auth_private.key \
  -in client_auth.crt \
  -name "DigiCert Client Auth Certificate"

At the password prompt, enter a strong password and remember it. You will need it for all mTLS API calls.

Delete sensitive files

The plaintext private key should not remain on your filesystem. The PKCS#12 file contains both the certificate and private key in a password-protected format.

# Delete plaintext private key
rm client_auth_private.key

Step 5: Test mTLS authentication

Verify the certificate works by making an API call using mTLS. To do this add clientauth to the base URL and use --cert flag to provide the certificate file (client_auth.p12) and password (YOUR_PASSWORD).

curl -X GET \
  --cert client_auth.p12:YOUR_PASSWORD \
  --cert-type P12 \
  "https://clientauth.demo.one.digicert.com/account/api/v1/user/me"
import requests
from cryptography.hazmat.primitives.serialization import pkcs12, Encoding, PrivateFormat, NoEncryption

# Configuration
BASE_URL = "https://clientauth.demo.one.digicert.com"
CERT_FILE = "client_auth.p12"
CERT_PASSWORD = "YOUR_PASSWORD"

# Load PKCS12 certificate
with open(CERT_FILE, "rb") as f:
    p12_data = f.read()

# Extract certificate and private key
private_key, certificate, additional_certs = pkcs12.load_key_and_certificates(
    p12_data,
    CERT_PASSWORD.encode()
)

# Convert to PEM format for requests
cert_pem = certificate.public_bytes(Encoding.PEM)
key_pem = private_key.private_bytes(
    Encoding.PEM,
    PrivateFormat.TraditionalOpenSSL,
    NoEncryption()
)

# Write temporary PEM files
with open("temp_cert.pem", "wb") as f:
    f.write(cert_pem)

with open("temp_key.pem", "wb") as f:
    f.write(key_pem)

# Get user details using client certificate authentication
response = requests.get(
    f"{BASE_URL}/account/api/v1/user/me",
    cert=("temp_cert.pem", "temp_key.pem")
)

print(f"Status Code: {response.status_code}")
print(response.json())
import java.io.FileInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

public class Example {
    public static void main(String[] args) throws Exception {
        // Configuration
        String baseUrl = "https://clientauth.demo.one.digicert.com";
        String certFile = "client_auth.p12";
        String certPassword = "YOUR_PASSWORD";
        
        // Load PKCS12 certificate
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream fis = new FileInputStream(certFile)) {
            keyStore.load(fis, certPassword.toCharArray());
        }
        
        // Configure SSL context with client certificate
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(
            KeyManagerFactory.getDefaultAlgorithm()
        );
        kmf.init(keyStore, certPassword.toCharArray());
        
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, null);
        
        // Get user details using client certificate authentication
        HttpClient client = HttpClient.newBuilder()
            .sslContext(sslContext)
            .build();
            
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + "/account/api/v1/user/me"))
            .GET()
            .build();
        
        HttpResponse<String> response = client.send(
            request, 
            HttpResponse.BodyHandlers.ofString()
        );
        
        System.out.println("Status Code: " + response.statusCode());
        System.out.println(response.body());
    }
}
using System;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Configuration
        string baseUrl = "https://clientauth.demo.one.digicert.com";
        string certFile = "client_auth.p12";
        string certPassword = "YOUR_PASSWORD";
        
        // Load PKCS12 certificate
        var certificate = new X509Certificate2(certFile, certPassword);
        
        // Create HttpClientHandler with client certificate
        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate);
        
        // Get user details using client certificate authentication
        using var client = new HttpClient(handler);
        
        var response = await client.GetAsync(
            $"{baseUrl}/account/api/v1/user/me"
        );
        
        string responseBody = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Status Code: {(int)response.StatusCode}");
        Console.WriteLine(responseBody);
    }
}

Expected response (HTTP 200):

{
  "id": "783a6a45-b74b-4635-bb4b-69855910ccd3",
  "email": "jane@example.com",
  "status": "ACTIVE",
  "access_scope": "account",
  "primary_account_id": "130deba5-3ebb-43b2-8183-1a9940d460f5",
  "created_at": "2021-06-23T08:55:19Z",
  "created_by": "8f7f8b1f-63c1-4580-af7d-ea4cd890e8f7",
  "user_type": "service",
  "friendly_name": "Example service user",
  "description": "",
  "locale": "en_US",
  "applications": [
    {
      "id": "2d3a72fd-ad44-4a9b-952a-2bcadb14a741",
      "name": "Trust Lifecycle",
      "permissions": [
        "VIEW_EM_SEAT",
        "VIEW_EM_AUDIT_LOG",
        "VIEW_EM_CERTIFICATE",
        "VIEW_EM_PROFILE"
      ]
    },
    {
      "id": "1a05282a-ec70-4da9-b921-933c070fcf80",
      "name": "IoT Trust",
      "permissions": [
        "VIEW_IOT_CERTIFICATE",
        "VIEW_IOT_ENROLLMENT_PROFILE"
      ]
    },
    {
      "id": "97b97f1b-8d1d-4203-a62c-0a209a1bea0a",
      "name": "Document Trust",
      "permissions": [
        "MANAGE_DSM_VIEW_CERTIFICATE_PROFILES",
        "MANAGE_DSM_ADD_VALIDATIONS",
        "MANAGE_DSM_VIEW_CERTIFICATE_TEMPLATES",
        "MANAGE_DSM_VIEW_VALIDATIONS"
      ]
    },
    {
      "id": "78c0355a-e1ca-4978-b60c-e9d66b9e1f30",
      "name": "Account Manager",
      "permissions": [
        "MANAGE_AM_ACCOUNT",
        "VIEW_AM_AUDIT_LOG"
      ]
    },
    {
      "id": "fd2b688d-43bd-4b4a-9fd6-f883ad9e813d",
      "name": "CA Manager",
      "permissions": [
        "VIEW_CM_LICENSE"
      ]
    },
    {
      "id": "7660bdb3-66e7-46e6-928f-dcae4d64ee91",
      "name": "Software Trust",
      "permissions": [
        "MANAGE_SM_CERTIFICATE_PROFILE",
        "VIEW_SM_CERTIFICATE",
        "SIGN_SM_HASH"
      ]
    }
  ],
  "accounts": [
    {
      "id": "4b13f12d-c8b2-4c49-a21f-9b399364f2ce",
      "name": "Example account",
      "active": true,
      "service_period": {
        "from": "2021-05-06",
        "to": "2030-05-06"
      },
      "friendly_identifier": "1234567",
      "locale": "en_US"
    }
  ]
}

Step 6: Manage certificate access (enable/disable)

Client authentication certificates can be temporarily disabled to revoke API access without deleting the certificate. To do this you need the certificate ID, which you can get from the id field of the response.json file from step 2.

CERT_ID=$(jq -r '.id' response.json)
echo "Certificate ID: $CERT_ID"

Disable the certificate

Add the certificate ID to the endpoint path /account/api/v1/client-auth-certificate/CERT_ID/enable and provide the required body content. Make sure to use API token authentication (x-api-key header) and remove the clientauth. from the base URL.

curl -X PUT "https://demo.one.digicert.com/account/api/v1/client-auth-certificate/${CERT_ID}/enable" \
  -H "x-api-key: SERVICE_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'

Response (HTTP 200):

{
  "id": "CERTIFICATE_ID",
  "user_id": "USER_ID",
  "name": "Certificate name",
  "common_name": "COMMON_NAME",
  "end_date": "2023-04-30T23:59:59Z",
  "start_date": "2021-06-10T00:00:00Z",
  "enabled": false,
  "serial_number": "AUTH_CERT_SERIAL",
  "public_key": "PUBLIC_KEY"
}

Verify access is revoked

Try making an mTLS API call with the disabled certificate.

curl -X GET \
  --cert client_auth.p12:YOUR_PASSWORD \
  --cert-type P12 \
  "https://clientauth.one.digicert.com/account/api/v1/user/me"

Response (HTTP 403):

{
    "errors": [
        {
            "code": "AUTHORIZATION_ERROR",
            "message": "No authentication data provided"
        }
    ]
}

Re-enable the certificate

curl -X PUT "https://demo.one.digicert.com/account/api/v1/client-auth-certificate/${CERT_ID}/enable" \
  -H "x-api-key: SERVICE_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

The returned enable field shows true.

Client certificate expiration and rotation

Client authentication certificates expire on the date specified during creation (maximum 397 days from creation date).

Check certificate expiration

Using OpenSSL:

openssl pkcs12 -in ~/.digicert/certs/client_auth.p12 -nokeys -passin pass:YOUR_PASSWORD | \
  openssl x509 -noout -enddate

Expected output:

notAfter=Oct 20 23:59:59 2026 GMT

Using the API:

curl -X GET \
  "https://demo.one.digicert.com/account/api/v1/client-auth-certificate" \
  -H "x-api-key: SERVICE_API_KEY" | jq '.[] | {name, end_date, enabled}'

What happens at expiration

When a certificate expires:

  • mTLS authentication immediately fails with HTTP 403
  • All integrations using the certificate stop working
  • The certificate cannot be renewed or extended
  • The certificate must be regenerated

Best practices for certificate rotation

Set up monitoring (90 days before expiration):

# Check expiration and alert if < 90 days remain
EXPIRY=$(openssl pkcs12 -in client_auth.p12 -nokeys -passin pass:YOUR_PASSWORD | \
  openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_REMAINING=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))

if [ $DAYS_REMAINING -lt 90 ]; then
  echo "WARNING: Certificate expires in $DAYS_REMAINING days. Rotate soon."
fi

Rotation workflow (zero-downtime):

  1. Generate new certificate 30-60 days before expiration (follow Steps 1-4)
  2. Test new certificate in non-production environment
  3. Update application configuration to use new certificate
  4. Deploy updated configuration
  5. Verify new certificate working
  6. Disable old certificate (Step 6)
  7. Monitor for any failed authentications
  8. Delete old certificate files after 7-day grace period

Common errors and solutions

{
  "errors": [
    {
      "code": "duplicate_error",
      "message": "Cannot have duplicate names for client authentication certificate"
    }
  ]
}

Choose a different `common_name` for the client certificate.
{
    "errors": [
        {
            "code": "no_access",
            "message": "Not enough permissions"
        }
    ]
}
{
    "errors": [
        {
            "code": "invalid_input_field",
            "message": "Wrong input or input format"
        }
    ]
}