# Client Certificates and mTLS in a GUI Client vs a Spec-Driven Flow

## Quick answer

Mutual TLS lets the server authenticate the client with a certificate instead of a bearer token, and it is notoriously awkward to configure in a GUI client. The certificate and key have to be selected per environment, expirations are hidden, and the setup rarely travels with the collection. A spec-driven flow keeps the mTLS material in configuration, loads it per request, and fails loudly when the certificate is missing or expired rather than producing a cryptic handshake error. If your platform uses mTLS, treat the client certificate as first-class configuration with the same care you give to secrets, not as a setting buried in a preferences dialog.

## How the mTLS handshake differs

In ordinary TLS only the server presents a certificate. With mutual TLS, the server requests one and the client must respond with its own, so the client needs both a certificate and a private key.

| Step | Ordinary TLS | Mutual TLS |
| --- | --- | --- |
| Server certificate | Presented and verified | Presented and verified |
| Client certificate request | Not sent | Server asks for one |
| Client material needed | None | Certificate plus private key |
| Failure mode | CA or hostname errors | Handshake abort if no valid cert |

## Wiring the certificate in code

Load the certificate and key from files referenced by environment configuration, and attach them to the agent. Never commit the key; reference the path and keep it protected.

Supplying the CA explicitly is important in internal PKI setups where the issuer is not in the system trust store. Keeping `rejectUnauthorized` enabled prevents the silent bypass that defeats the point of the check.

## GUI clients versus configuration

A desktop client lets you pick a certificate in a settings panel, which works for ad-hoc debugging but creates three problems: the binding is machine-specific and does not version with the request, a teammate must repeat the same manual setup, and an expired certificate surfaces as a generic handshake alert with no clear source. Configuration-based loading solves all three because the paths and environment are declared, the same setup runs in CI, and the certificate can be checked before the call.

- Reference certificate and key by environment-specific paths, never embed them.
- Keep file permissions on the private key restricted to the owner.
- Add a startup check that the files exist and parse before any request is made.
- Log a clear error naming the certificate problem rather than the raw TLS alert.

## Handling rotation and expiration

Client certificates rotate on a schedule, so build a check that reads the certificate's not-after date and warns with lead time, the same way you monitor token expiry. In automated tests, use a short-lived certificate generated by the test CA so the mTLS path is exercised without real material, and include a negative test that presents no certificate and confirms the server rejects the handshake. This proves the mutual requirement is actually enforced rather than merely configured.

## Certificate chains and common-name validation

Two practical details cause most mTLS debugging sessions. First, the client must present not just the leaf certificate but the full chain up to a certificate authority the server trusts; presenting only the leaf makes the server unable to build a trust path and abort the handshake even though the file looks correct. Second, the certificate's subject and any subject alternative names should identify the client in a way your authorization layer can map to a principal, so keep the naming convention consistent and avoid relying on a deprecated common name alone. Validate the parsed certificate in the startup check, printing the issuer, subject, and expiration so a mismatch is obvious before the call. When a handshake fails, walk the chain on both sides rather than swapping files blindly; the defect is almost always an untrusted issuer, a key mismatch, or a clock that makes a valid certificate look expired, and each is confirmed by reading the certificate rather than retrying the request.

## Where mTLS fits versus bearer tokens

mTLS is strongest for service-to-service communication where identity can be provisioned and rotated centrally, and where the additional certificate management is justified. For user-facing clients with interactive login, short-lived bearer tokens are usually simpler. The decision is operational: choose mTLS when the platform already runs a certificate authority and can distribute certs, and do not adopt it just to avoid managing tokens, because it introduces its own lifecycle burden.

## FAQ

### Why do I get a handshake failure even though the file is selected?

Possible causes include a private key that does not match the certificate, an issuer the server does not trust, or an expired certificate. Validate the three files together before the request, because the raw TLS alert does not identify which one is wrong.

### Can I share the client certificate across the team?

Prefer separate certificates per identity so access can be revoked individually, and distribute them through configuration rather than a shared copy. A single shared certificate cannot be revoked without affecting everyone and removes individual accountability.

### How do I test mTLS in CI without real secrets?

Generate a test CA and a short-lived client certificate within the pipeline, point the server at the test CA, and run both a positive call with the cert and a negative call without one. No production material is needed.

### Does mTLS replace application-level authorization?

No. It authenticates the client identity, but the application still decides what that identity is allowed to do. Keep authorization checks in place even when the TLS handshake succeeds.

This is part of the [Powerduck vs Insomnia: API Client and Contract Testing](https://www.powerduck.com/blog/powerduck-vs-insomnia/) — start there for the full client comparison.

