Implementing zero trust networking from scratch. Covers identity-based access, microsegmentation, mTLS, network policies, and replacing VPNs with zero trust proxies.
A Zero Trust Network Architecture (ZTNA) enforces strict identity-based access control for every user and device, regardless of network location, ensuring that only verified entities can reach protected resources. It matters when the network perimeter is no longer sufficient—when users access services from home, mobile devices, or cloud environments, and when data breaches and lateral movement are common. In this model, every request is authenticated, authorized, and encrypted, with continuous validation.
Zero Trust begins with the assumption that the network is hostile and that trust must be earned, not assumed. Every access attempt—from a laptop to a microservice—follows a consistent pattern: identity verification, policy enforcement, and dynamic access control.
The core flow involves three phases: discovery, authentication, and policy evaluation.
When a user or device attempts to reach a protected resource, the first step is discovery: identifying the requesting entity’s attributes. This includes:
These attributes are collected and bound to a session. A typical HTTP request includes:
GET /api/users HTTP/1.1
Host: app.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Client-IP: 192.168.1.10
X-Device-Id: 45c7d1f3-8a2e-4b9c-bc5b-8f9d2c7a6e1b
X-Posture-Score: 92
X-Client-Cert: -----BEGIN CERTIFICATE-----
MIIE...-----END CERTIFICATE-----
The receiving service validates the Authorization header (JWT or OAuth Bearer), extracts the user identity, and checks the X-Client-IP and X-Device-Id to enrich the identity context.
Once identity and context are established, the system evaluates a policy engine that defines access rules. Policies are evaluated in order, with early decisions returning a result and later ones refining it.
A policy rule might look like:
{
"name": "Allow internal developers to access staging API",
"conditions": [
{
"type": "user-attribute",
"key": "department",
"value": "engineering",
"operator": "equals"
},
{
"type": "device-posture",
"key": "os",
"value": "macOS",
"operator": "in",
"values": ["macOS 12", "macOS 13", "macOS 14"]
},
{
"type": "time",
"start": "09:00:00",
"end": "18:00:00"
}
],
"actions": [
{
"type": "grant-access",
"duration": "2h",
"scopes": ["read:users", "write:logs"]
},
{
"type": "apply-mtls",
"client-cert": true,
"server-cert": true
}
],
"priority": 10
}
The policy engine evaluates all conditions. If all match, the actions are applied: access is granted, and the client receives a session token with scopes and metadata.
Access is not a one-time event. The system maintains a session state that is continuously validated. The client periodically sends a heartbeat to the policy engine.
POST /api/session/heartbeat HTTP/1.1
Host: auth.example.com
Authorization: Bearer <session-token>
Content-Type: application/json
{
"last-active": "2024-05-15T14:32:10Z",
"network-latency": 42,
"battery-level": 78,
"location": {
"latitude": 37.7749,
"longitude": -122.4194,
"accuracy": 50
}
}
The server checks for:
duration in policy)If any validation fails, the system can:
Zero Trust relies on a set of well-established, standard protocols and tools. No single product dominates, but common patterns emerge.
Authentication is centralized, typically using OAuth 2.0 or OpenID Connect. A client acquires a refresh token and access token upon initial login.
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=4d8c3a2b1e0f&
redirect_uri=https://app.example.com/callback&
client_id=app-12345&
code_verifier=xyz9876543210
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "a1b2c3d4e5f6...",
"expires_in": 3600,
"scope": "openid profile email read:users write:logs",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
The access token is used in subsequent requests. Upon expiration, the client refreshes it:
POST /oauth/refresh HTTP/1.1
Host: auth.example.com
Authorization: Bearer <refresh-token>
Content-Type: application/json
{
"refresh_token": "a1b2c3d4e5f6..."
}
All traffic is end-to-end encrypted, with mTLS (mutual TLS) as the default transport security.
A service configures mTLS via OpenSSL and Nginx, with the following nginx.conf snippet:
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate_file /etc/ssl/certs/app.crt;
ssl_certificate_key_file /etc/ssl/private/app.key;
ssl_client_certificate /etc/ssl/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
location /api {
proxy_pass http://backend-service;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Client-Cert $ssl_client_cert;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/ssl/ca.crt;
}
}
Clients must present a valid certificate signed by a trusted CA. The server verifies the client’s certificate chain, checks for revocation (via CRL or OCSP), and validates the identity.
Device attestation ensures that the client is not only present but also trusted. This is achieved using TCB (Trusted Computing Base) reports.
A client generates a TPM (Trusted Platform Module) report and sends it via TCG (Trusted Computing Group) protocols.
{
"measurements": [
{
"hash-algorithm": "SHA256",
"pcr-index": 0,
"measurement": "0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b"
},
{
"pcr-index": 1,
"measurement": "1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d"
}
],
"platform-info": {
"firmware-version": "1.2.3",
"bootloader": "GRUB",
"os-version": "Ubuntu 22.04 LTS"
},
"report-signature": "base64-encoded-signature"
}
The attestation service validates the report against a known baseline—a set of expected PCR values. If any measurement deviates beyond a threshold, the device is considered non-trusted.
The policy engine is stateful and supports rule chaining and context merging. It runs in a microservice that receives policy decisions via gRPC.
service PolicyEngine {
rpc Evaluate(EvaluationRequest) returns (EvaluationResponse);
}
message EvaluationRequest {
string subject_id = 1;
string resource_id = 2;
string action = 3;
map<string, string> context = 4;
}
message EvaluationResponse {
bool allowed = 1;
repeated string scopes = 2;
string session_token = 3;
int32 validity_seconds = 4;
repeated PolicyAction actions = 5;
}
An evaluation request might be:
{
"subject_id": "user:[email protected]",
"resource_id": "api:users",
"action": "read",
"context": {
"device_id": "dev-abc123",
"ip": "192.168.1.10",
"location": "San Francisco",
"posture_score": "92"
}
}
The engine returns a EvaluationResponse with allowed: true, scopes: ["read:users"], and session_token for use in future requests.
Several subtle failure modes are common in ZTNA deployments.
A common failure is missing intermediate certificates in the TLS handshake. The client sends its certificate, but the server cannot validate it due to an untrusted root or missing intermediate.
Error from openssl s_client:
$ openssl s_client -connect app.example.com:443 -cert client.crt -key client.key -CAfile ca.crt
...
SSL handshake has read 3241 bytes and written 488 bytes
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA512
...
Verify return code: 21 (unable to verify the certificate chain)
The Verify return code: 21 indicates a chain validation error. The solution is to ensure the entire chain is sent:
client.crt, intermediate.crtserver.crt, intermediate.crt, root.crtThe client must also validate the chain using openssl verify:
$ openssl verify -CAfile ca.crt -untrusted intermediate.crt client.crt
client.crt: OK
When a session token expires, the client must refresh it. If the refresh request fails, the user experiences a silent outage.
Common failure:
POST /oauth/refresh with refresh_token401 Unauthorized, WWW-Authenticate: Bearer error="invalid_token"The error payload might be:
{
"error": "invalid_token",
"error_description": "The access token is invalid or expired.",
"expires_in": 300,
"token_type": "Bearer"
}
This indicates the token is expired or malformed. The client should:
refresh_tokenexpires_in to schedule refreshsDevice posture reports may lag. A user connects from a hotel, but the posture service takes 30 seconds to update the device’s status.
The result: a request is evaluated against outdated posture data. The policy grants access based on "device up to date", but the device has not yet installed the latest security patch.
To detect this, systems use event-driven posture updates:
POST /posture/submit with a new reportpolicy-evaluation eventpolicy-engine service listens on a message queue (e.g., RabbitMQ, Kafka) and evaluates all pending sessionsA misconfiguration can delay evaluation: if the queue backlog grows, sessions are evaluated hours after they occurred.
ZTNA often requires network path validation—ensuring that traffic from client to server traverses a secure, monitored path.
The system may use ICMP echo requests or gRPC health checks to validate the path.
$ ping -c 5 app.example.com
PING app.example.com (10.0.1.10): 56 data bytes
64 bytes from 10.0.1.10: icmp_seq=1 ttl=54 time=34.2 ms
64 bytes from 10.0.1.10: icmp_seq=2 ttl=54 time=33.8 ms
...
---
5 packets transmitted, 5 received, 0% packet loss, time 4003ms
rtt min/avg/max/stddev = 33.804/34.136/35.121/0.578 ms
If rtt max > 100ms or packet loss > 5%, the system flags a network degradation event and may re-evaluate policies.
This workflow becomes the foundation of a resilient, identity-driven network—where every service, every user, and every device is a trusted entity, validated on demand.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy