Skip to main content
Guides

Mutual TLS (mTLS) for a generated API

Configure mTLS authentication for your RestWard API, including CA, fingerprint, and subject criteria, certificate rollover, per-method rate limits, IP allowlists, and client examples.

Mutual TLS (mTLS) authenticates API consumers using X.509 client certificates. RestWard supports mTLS alongside API keys and JWT, letting you choose the right authentication method for your security requirements.

This guide covers configuring mTLS for a generated API, the available authentication criteria, certificate rollover, per-method rate limits and IP allowlists, and client examples.

Prerequisites

  • A RestWard organization with at least one generated API
  • mTLS authentication method enabled for your organization
  • Client certificates ready for your consumers

How mTLS works in RestWard

RestWard's data-plane APIs are served behind a TLS-terminating reverse proxy (Caddy). The proxy handles the TLS handshake and forwards the client certificate to the RestWard runtime in the X-Forwarded-Client-Cert header as base64-encoded DER. The runtime then validates the certificate against your configured criteria.

Architecture: Caddy terminates TLS -> forwards client cert in X-Forwarded-Client-Cert header -> RestWard runtime validates against CA/fingerprint/subject criteria.

This means:

  • You configure mTLS in the RestWard dashboard, not in the proxy
  • The proxy requests but does not validate the client certificate: on the wildcard *.run.restward.tech, which covers every API's own hostname, Caddy asks for a client certificate in client_auth mode request, so it is optional and API-key or JWT requests are unaffected
  • Certificate validation happens in the runtime using your configured trust anchors
  • mTLS can coexist with API key and JWT authentication; the runtime tries each enabled method until one succeeds

Enabling and configuring mTLS

mTLS is configured per organization, not per API. The configuration applies to all APIs in the organization that have the mTLS authentication method enabled.

Source: src/Domain/Entities/AuthMethodConfiguration.cs:L6-L10 — AuthMethodConfiguration has OrganizationId, Method, IsEnabled, and Configuration; there is no ApiConfigurationId. The same configuration is shared by all APIs in the organization.

Enabling mTLS

  1. Go to Settings → Authentication Methods
  2. Find Mutual TLS (mTLS) in the list
  3. Toggle the switch to Enabled
  4. Click Save

Configuring mTLS

Once enabled, configure the mTLS authentication method for your organization:

  1. In Settings → Authentication Methods, click Configure for the mTLS entry
  2. Configure the criteria:
    • Trusted CA (PEM): Paste your CA certificate(s) in PEM format (can include multiple certificates)
    • Allowed SHA-256 fingerprints: Add SHA-256 thumbprints of specific client certificates (one per line)
    • Allowed subjects: Add subject distinguished names, common names, or SAN DNS names (one per line)
  3. Optionally configure:
    • Default scope: The permission level granted to authenticated clients (Read Only, Read & Write, or Admin)
    • Rate limit (/min per certificate): Per-certificate rate limit (defaults to 60 if not set)
    • Allowed IP Ranges: CIDR ranges or IP addresses from which certificates are accepted
  4. Click Save

Source: src/Web/Components/Pages/AuthMethods/MtlsAuthConfig.razor:L1-L10 — The configuration modal is titled "Configure Mutual TLS" and contains fields for Trusted CA, Allowed SHA-256 fingerprints, Allowed subjects, Default scope, Rate limit, and IP allowlist.

You specify which client certificates are accepted using one or more of these criteria:

Criterion Description Format Notes
Trusted CA Certificates must chain to this CA PEM-encoded CA certificate(s) Required when using subject criteria
Allowed Fingerprints Specific certificate thumbprints to accept SHA-256 hex strings (with or without colons) Exact match required
Allowed Subjects Specific subject DNs, CNs, or SAN DNS names to accept String patterns Only checked when CA chain validates; subjects alone are not sufficient

Important: At least one criterion must be configured. If you specify allowed subjects, you must also configure a trusted CA - a subject match without CA validation is not accepted (anyone can self-sign a certificate with any subject).

Configuration steps

  1. In Settings → Authentication Methods, click Configure for the mTLS entry
  2. Configure the criteria:
    • Trusted CA (PEM): Paste your CA certificate(s) in PEM format (can include multiple certificates)
    • Allowed SHA-256 fingerprints: Add SHA-256 thumbprints of specific client certificates (one per line)
    • Allowed subjects: Add subject distinguished names, common names, or SAN DNS names (one per line)
  3. Optionally configure:
    • Default scope: The permission level granted to authenticated clients (Read Only, Read & Write, or Admin)
    • Rate limit (/min per certificate): Per-certificate rate limit (defaults to 60 if not set)
    • Allowed IP Ranges: CIDR ranges or IP addresses from which certificates are accepted
  4. Click Save

Validation rules

The configuration is validated when saved:

  • At least one criterion (CA, fingerprint, or subject) must be present
  • If subjects are specified, a trusted CA must also be specified
  • Blank entries in fingerprints or subjects lists are ignored

When a client connects:

  1. If no certificate is presented -> mTLS authentication is not attempted (other methods may still work)
  2. If a certificate is presented but no criteria are configured -> authentication fails with 401 Unauthorized
  3. If a certificate is presented and criteria are configured:
    • Certificate must be within its validity window (not before NotBefore, not after NotAfter)
    • Certificate must match at least one criterion:
      • Its SHA-256 thumbprint matches an allowed fingerprint, OR
      • It chains to the trusted CA AND (if subjects are configured) its subject matches an allowed subject
  4. If accepted -> authentication succeeds with the configured scope and rate limit
  5. If rejected -> authentication fails with 401 Unauthorized and reason not_accepted or outside_validity

Authentication criteria in detail

Trusted CA

The trusted CA certificate(s) form the root of trust for your mTLS configuration. Client certificates must chain to one of these CAs to be accepted (unless they match a fingerprint criterion).

Format: PEM-encoded certificate(s). You can paste multiple CA certificates; they are all added to the trust store.

Example PEM:

Text
-----BEGIN CERTIFICATE-----
MIIFazCCBFOgAwIBAgIRAIIQz7DSQONZqGDYzMiP0w0wDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh
... (certificate content) ...
-----END CERTIFICATE-----

Best practices:

  • Use intermediate CAs for better certificate management
  • Rotate your CA certificates periodically
  • Keep CA private keys secure and offline

Allowed Fingerprints

Fingerprint criteria accept client certificates whose SHA-256 thumbprint matches one of the configured values. This is useful when you want to accept specific, known certificates regardless of their CA chain.

Format: SHA-256 hexadecimal string, with or without colons. Both A1B2C3D4... and A1:B2:C3:D4... formats are accepted and normalized for comparison.

Example fingerprints:

Text
A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6C7D8E9F0A1B2
A1:B2:C3:D4:E5:F6:A7:B8:C9:D0:E1:F2:A3:B4:C5:D6:E7:F8:A9:B0:C1:D2:E3:F4:A5:B6:C7:D8:E9:F0

When to use fingerprints:

  • You have a small, known set of client certificates
  • You want to accept certificates from different CAs
  • You need fine-grained control over which specific certificates are accepted

When NOT to use fingerprints:

  • You have many clients with frequently rotating certificates
  • Managing individual fingerprints becomes burdensome

Allowed Subjects

Subject criteria accept client certificates whose subject distinguished name (DN), common name (CN), or subject alternative name (SAN) DNS entry matches one of the configured values.

Important: Subject matching only applies to certificates that also chain to the trusted CA. A subject match alone is not sufficient for authentication.

Format: String patterns matched against:

  • The full subject distinguished name (e.g., CN=MyApp, O=MyOrg, C=US)
  • The common name (CN) from the subject
  • The DNS names from subject alternative names (SAN)

Matching is case-insensitive.

Example subjects:

Text
CN=production-app
CN=MyApp, O=MyOrg, C=US
api.client.myorg.com

When to use subjects:

  • You want to accept any certificate from a particular organization or application
  • Combined with CA, you can accept all certificates issued by your CA for a specific subject
  • You have multiple services using certificates from the same CA

Certificate rollover

When client certificates expire or are compromised, you need to perform a rollover. RestWard supports smooth certificate rollover through overlapping acceptance criteria.

  1. Generate new certificates with the new CA or new key pairs
  2. Add the new criteria to your mTLS configuration:
    • Add the new CA to Trusted CA PEM (if changing CA)
    • Add new fingerprints to Allowed Fingerprints
    • Add new subjects to Allowed Subjects
  3. Save the configuration - both old and new certificates are now accepted
  4. Deploy new certificates to your clients
  5. Verify all clients are using the new certificates
  6. Remove old criteria from the configuration once all clients have migrated

Example: CA rollover

Text
# Before rollover
Trusted CA PEM: Old-CA-Cert

# During rollover (both accepted)
Trusted CA PEM: Old-CA-Cert, New-CA-Cert

# After rollover
Trusted CA PEM: New-CA-Cert

Example: Fingerprint rollover

Text
# Before rollover
Allowed Fingerprints: Old-Cert-Fingerprint

# During rollover (both accepted)
Allowed Fingerprints: Old-Cert-Fingerprint, New-Cert-Fingerprint

# After rollover
Allowed Fingerprints: New-Cert-Fingerprint

Tip: Use CA-based validation with subjects for easier rollover. You can issue new certificates from the same CA with the same subject, and they will be accepted automatically without configuration changes.

Per-method rate limits

Each mTLS configuration can have its own rate limit, applied per authenticated client certificate.

  • Default: 60 requests per minute (same as API keys)
  • Configurable: Set any value in the mTLS configuration
  • Scope: Per certificate thumbprint (SHA-256 hash of the client certificate's DER), across all endpoints of all APIs in the organization. The caller identifier used for the quota is cert:{thumbprint} as defined in ApiKeyRateLimitingMiddleware.CallerId (src/ApiRuntime/Middleware/ApiKeyRateLimitingMiddleware.cs:L179).
  • Enforcement: Uses the same rate-limiting infrastructure as API keys

When a client exceeds their rate limit, they receive:

  • HTTP 429 Too Many Requests
  • Retry-After header with seconds until the window resets
  • X-RateLimit-Scope: caller header indicating the caller quota was exceeded

Example rate-limited response:

HTTP
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 42
X-RateLimit-Scope: caller

{"error": "Rate limit exceeded. Try again later."}

Note: The per-method rate limit is separate from per-endpoint rate limits. Both apply: a request must pass both the caller quota and any endpoint-specific limit.

IP allowlists

You can restrict mTLS-authenticated clients to specific IP addresses or CIDR ranges. This adds an additional layer of security beyond certificate validation.

Configuration:

  • Add IP addresses or CIDR ranges in the Allowed IP Ranges field
  • One entry per line
  • Supports both IPv4 and IPv6

Examples:

Text
# Single IP
192.168.1.100

# CIDR range
192.168.1.0/24

# IPv6
2001:db8::/32

Behavior:

  • If no IP ranges are configured, clients can connect from any IP
  • If ranges are configured, the client's IP must be in at least one range
  • IP check happens after certificate validation
  • Rejection returns HTTP 403 Forbidden with message "IP address not allowed."

Important: The IP address used is the one resolved by the trusted proxy (after X-Forwarded-For processing). Ensure your proxy is correctly configured.

Client examples

Prerequisites for all examples

  • A RestWard API with mTLS configured, and its endpoint hostname from the API's detail page (the examples write it as YOUR-API-HOSTNAME)
  • A client certificate accepted by your configuration
  • The certificate's private key

curl

Use curl's --cert and --key options to present a client certificate:

Shell
# With separate certificate and key files
curl --cert client-cert.pem --key client-key.pem \
  https://YOUR-API-HOSTNAME/api/public/users

# With a combined PEM file (certificate + private key)
curl --cert client-combined.pem \
  https://YOUR-API-HOSTNAME/api/public/users

# With certificate, key, and CA bundle for server verification
curl --cert client-cert.pem --key client-key.pem --cacert ca-bundle.pem \
  https://YOUR-API-HOSTNAME/api/public/users

# With verbose output to see TLS handshake details
curl -v --cert client-cert.pem --key client-key.pem \
  https://YOUR-API-HOSTNAME/api/public/users

Note: curl automatically handles client certificate requests from the server. The --cert option sends the certificate when the server requests it.

.NET (HttpClientHandler)

In .NET, configure the HttpClientHandler to use a client certificate:

C#
using System;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Load the client certificate from a PFX file
        // X509Certificate2 constructor with password and key storage flags is obsolete on .NET 9+/10 (SYSLIB0057)
        // Use X509CertificateLoader.LoadPkcs12FromFile instead, which does not persist the private key
        var certificate = X509CertificateLoader.LoadPkcs12FromFile(
            "client-cert.pfx",
            "your-certificate-password");

        var handler = new HttpClientHandler
        {
            ClientCertificateOptions = ClientCertificateOption.Manual,
        };

        // Add the client certificate
        handler.ClientCertificates.Add(certificate);

        var client = new HttpClient(handler);
        client.DefaultRequestHeaders.Add("Accept", "application/json");

        try
        {
            var response = await client.GetAsync(
                "https://YOUR-API-HOSTNAME/api/public/users");
            
            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(content);
            }
            else
            {
                Console.WriteLine($"Error: {response.StatusCode}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
        }
    }
}

Notes:

  • X509CertificateLoader.LoadPkcs12FromFile is the recommended approach on .NET 9+/10; it loads the certificate without persisting the private key to the machine store
  • ClientCertificateOption.Manual means the certificate is sent only when explicitly requested by the server
  • For automatic certificate selection, use ClientCertificateOption.Automatic

Node.js (https.Agent)

In Node.js, create a custom HTTPS agent with your client certificate:

JavaScript
const https = require('https');
const fs = require('fs');

// Load certificate and key files
const cert = fs.readFileSync('client-cert.pem');
const key = fs.readFileSync('client-key.pem');
const ca = fs.readFileSync('ca-bundle.pem'); // Optional: for server cert verification

const agent = new https.Agent({
  cert: cert,
  key: key,
  ca: ca, // Optional: CA bundle to verify the server
  rejectUnauthorized: true // Set to false only for testing with self-signed certs
});

const options = {
  hostname: 'YOUR-API-HOSTNAME',
  port: 443,
  path: '/api/public/users',
  method: 'GET',
  agent: agent,
  headers: {
    'Accept': 'application/json'
  }
};

const req = https.request(options, (res) => {
  let data = '';
  
  res.on('data', (chunk) => {
    data += chunk;
  });
  
  res.on('end', () => {
    if (res.statusCode >= 200 && res.statusCode < 300) {
      console.log('Response:', data);
    } else {
      console.log('Error:', res.statusCode, data);
    }
  });
});

req.on('error', (error) => {
  console.error('Request error:', error);
});

req.end();

Alternative using axios:

JavaScript
const axios = require('axios');
const https = require('https');
const fs = require('fs');

const cert = fs.readFileSync('client-cert.pem');
const key = fs.readFileSync('client-key.pem');

const agent = new https.Agent({
  cert: cert,
  key: key
});

axios.get('https://YOUR-API-HOSTNAME/api/public/users', {
  httpsAgent: agent,
  headers: {
    'Accept': 'application/json'
  }
})
.then(response => {
  console.log(response.data);
})
.catch(error => {
  console.error('Error:', error.response ? error.response.data : error.message);
});

Troubleshooting

Common issues and solutions

Issue Cause Solution
401 Unauthorized - "Mutual TLS is not configured for this API" mTLS not configured for the API Enable and configure mTLS for the API
401 Unauthorized - "Client certificate rejected" with reason not_accepted Certificate doesn't match any criterion Verify certificate matches CA/fingerprint/subject
401 Unauthorized - "Client certificate rejected" with reason outside_validity Certificate is expired or not yet valid Check certificate dates and renew if needed
403 Forbidden - "IP address not allowed" Client IP not in allowed ranges Add client IP to allowed ranges or remove IP restriction
429 Too Many Requests Rate limit exceeded Wait for the window to reset or increase the limit
Connection refused / timeout Server not configured for mTLS Verify Caddy is configured with client_auth mode

Verifying your certificate

Check your certificate's details:

Shell
# View certificate details
openssl x509 -in client-cert.pem -text -noout

# Get SHA-256 fingerprint
openssl x509 -in client-cert.pem -fingerprint -sha256 -noout

# Get subject
openssl x509 -in client-cert.pem -subject -noout

# Get SAN (Subject Alternative Names)
openssl x509 -in client-cert.pem -ext subjectAltName -noout

Testing with a self-signed certificate

For testing purposes, you can create a self-signed certificate:

Shell
# Generate CA
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=Test CA"

# Generate client certificate
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=test-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256

# Create combined PEM (certificate + key)
cat client.crt client.key > client-combined.pem

Then configure RestWard with:

  • Trusted CA PEM: contents of ca.crt
  • Allowed Subjects: CN=test-client

Checking the forwarded header

To verify that Caddy is correctly forwarding the certificate, you can temporarily add logging or use a test endpoint. The header should contain the base64-encoded DER of the client certificate.

Reconnecting to server...

Please wait while we restore your connection

Connection lost

Retrying in seconds...

Unable to reconnect

The server connection has been lost.
You may need to log in again.

Session Paused

The server has temporarily paused your session.

Session Expired

Your session could not be resumed.
Please reload the page to continue.