Adding authentication and logging when you cannot touch the client's code
A legacy application nobody dares to modify can still get security and logging. You need to know where to put the proxy and keep the application's own work out of it.

In brief
- A gateway sits in front of services and handles authentication, TLS, logging and throttling. A sidecar runs next to each instance. An ambassador runs next to the client and handles outbound calls.
- A gateway only works if the backend rejects every request that does not come through it, and it must never contain business logic.
- An ambassador should retry only idempotent operations. A gateway must be designed and load tested so it does not become a single point of failure.
Picture a Monday morning at a client’s office. Their order management system is a Java application written years ago. The person who wrote it has left, there are no tests, and nobody dares to redeploy it.
The request you receive: by next week the API needs authentication, logs the security team can read, and part of the traffic moved to a new reporting service.
Many engineers’ first instinct is to open the code and change it. For an FDE that is usually the worst option. The code is not yours, the client carries all the regression risk, and few people will sign off on a new build within a week.
The skill needed here is adding capabilities to a system from the outside, with a proxy in the right place.
Microsoft Azure’s architecture documentation groups this work into a few named patterns: gateway, sidecar, ambassador, and gatekeeper, a variant focused on security. Once you know them, the next time you hear “you can’t change the code, but it needs new features”, you will know where the proxy should go.
Where the proxy sits decides what it can do
Follow the path of a request. It starts at the client, crosses the network and reaches the service. The three patterns differ in where the proxy stands on that path.
Gateway sits in front of a group of services. As the Gateway Routing pattern describes it, the gateway routes at Layer 7, so clients only need to know a single endpoint. You can add, split or reorganise the services behind it without changing the client. The Gateway Offloading pattern goes a step further: the gateway takes over cross-cutting work from the backend, including certificate management, authentication, TLS termination, monitoring, protocol translation and throttling.
Sidecar is a process or container that runs alongside the application. Each instance gets its own sidecar, and the two share a lifecycle. Because it runs separately, a sidecar does not depend on the application’s language. Service meshes such as Istio use sidecar proxies to provide mTLS, retries and telemetry without changes to application code.
Ambassador is also an out-of-process proxy, but it sits on the same host as the caller. It handles monitoring, logging, routing, TLS and resiliency for outbound calls. Microsoft suggests this pattern for extending the networking capabilities of legacy applications or applications that are hard to modify.
| Gateway | Sidecar | Ambassador | |
|---|---|---|---|
| Position | In front of a group of services | Next to each service instance | Next to the client, on the same host |
| Main traffic direction | Inbound | In and out of one instance | Outbound |
| Best when | You need a shared entry point with authentication and logging | You need mTLS and telemetry between services | An old app calls external APIs without TLS, retries or logging |
| Cost | Can become a single point of failure and a bottleneck | Adds latency and resources to every instance | Adds latency; retries can have consequences |
Example: a gateway in front of the order application
Back to the hypothetical client. Say the order application listens on port 8080, the new reporting service on port 9000, and the client’s IT team already runs a token-checking service. A minimal nginx configuration acting as a gateway might look like this:
log_format json escape=json
'{"time":"$time_iso8601","method":"$request_method",'
'"uri":"$request_uri","status":$status,'
'"rt":$request_time,"user":"$auth_user"}';
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/api.crt;
ssl_certificate_key /etc/nginx/certs/api.key;
access_log /var/log/nginx/api.json json;
location = /_auth {
internal;
proxy_pass http://auth-svc/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /api/orders/ {
auth_request /_auth;
auth_request_set $auth_user $upstream_http_x_user;
proxy_pass http://legacy-orders:8080;
}
location /api/reports/ {
auth_request /_auth;
auth_request_set $auth_user $upstream_http_x_user;
proxy_pass http://reports-v2:9000;
}
}
Read the file against the client’s three requirements. Authentication lives in auth_request: every request must pass through /_auth first, and the Java application knows nothing about it. Logging lives in log_format json: each request produces one JSON line with the time, status code, latency and caller, which the security team can feed straight into their own system.
Routing lives in the two location blocks. Clients know a single domain, while /api/reports/ quietly goes to the new service. If the new version has a bug, rolling back means editing one proxy_pass line and reloading the gateway. As the Gateway Offloading pattern notes, even when the services behind it are not properly instrumented, the gateway still gives you a baseline of monitoring and logging.
What you must do once the configuration works
The configuration above is still not safe if the order application keeps port 8080 open on the internal network. Anyone who knows the address can call it directly, skip authentication and leave no log line behind.
Microsoft’s guidance is explicit that the backend should only accept requests that come through the gateway. In practice you can enforce this with a firewall, security group or network policy that only allows the gateway’s IP to connect.
The Gatekeeper pattern goes further. The intermediary layer that validates and sanitises requests runs with limited privileges, while the application keeps access to storage and services. Applied to the example: the gateway should not hold database passwords or backend keys, so that if it is compromised the damage is contained.
On site, roll it out in this order. First run the gateway in parallel, logging only, with authentication off, to see what real traffic looks like. Then turn on authentication route by route, then block direct access to the backend, and only then shift traffic gradually to the new service.
Sidecars and ambassadors: where the gateway cannot reach
A gateway only controls traffic that comes through the front door. When services inside a cluster call one another, or when an old application calls a partner’s API, you need a proxy closer to the action.
Say the client runs on Kubernetes and wants traffic between services encrypted. A sidecar proxy in each pod, for example through Istio, provides mTLS and collects telemetry without the application knowing.
Istio’s documentation describes a service mesh as delivering zero-trust security, observability and advanced traffic management, with two modes: ambient or traditional sidecar.
If the order application calls a payment API over plain HTTP, with no timeouts and no logging, put an ambassador on the same host. The application calls localhost; the ambassador handles TLS, logs every outbound call and enforces timeouts. The old code stays as it is.
Four common traps
The first trap is leaving the backend open, as described above. The second is more dangerous because it comes from good intentions: the client asks you to “check credit limits at the gateway while you’re at it”. Microsoft’s guidance is blunt: never push business logic into the gateway.
The same temptation appears with Gateway Aggregation, where the gateway calls several backends and merges the results so the client makes fewer calls. If the aggregation logic starts getting complicated, move it into a separate aggregation service behind the gateway.
The third trap is enabling retries in the ambassador for everything. Microsoft warns that retries in an ambassador may not be safe if operations are not idempotent.
Imagine a create-payment call that times out on the response but actually succeeded: the proxy sends it again, and your client’s customer is charged twice.
Retry only operations such as GET, or writes that carry an idempotency key.
The fourth trap is forgetting that every request now passes through the gateway. It can become a single point of failure and a bottleneck, so load test it before production and design it to the client’s availability requirements, for example by running several instances.
Sidecars have their own costs: added latency, wasted resources for small applications, and they may be unnecessary if the client’s mesh already offers a sidecar-less data plane.
Can you pick the right proxy for these three cases?
Answer before reading on. Case A: an old reporting application calls a vendor’s API over plain HTTP, and the client wants TLS and a log of every outbound call. Case B: an external partner needs a single authenticated endpoint to reach three internal services.
Case C: services on Kubernetes call one another, and the security team wants that traffic encrypted and telemetry collected without changing code.
Answers: A is an ambassador, because the problem is outbound traffic on the client side. B is a gateway, because it needs a shared entry point for authentication and routing.
C is a sidecar proxy in a service mesh, or ambient mode if the client’s mesh supports it and it meets the requirements. If you got all three, you have the core question: which way does the request travel, and where on that path does the proxy stand?
How to show this skill on your CV
When reading job descriptions for FDE or solutions engineer roles, look for phrases such as “integrate with legacy systems”, “API gateway”, “service mesh” and “observability”.
On your CV, do not just write “nginx, Istio”. Write in terms of outcomes, for example: added authentication and structured logging to a legacy system through a gateway, with no changes to its code, and locked down direct access to the backend. That line shows an interviewer you understand both the technology and the client’s constraints.
The next time someone says “nobody dares to touch this system”, do not treat it as a dead end. It is usually the moment to ask where requests travel and where a proxy should go.
Was this article useful?
Thanks for the feedback!
7 sources
- Gateway Routing pattern - Azure Architecture Center | Microsoft Learn · 2022-08-29
- Gateway Offloading Pattern - Azure Architecture Center | Microsoft Learn · 2026-09-23
- Gateway Aggregation Pattern - Azure Architecture Center | Microsoft Learn · 2026-06-02
- Sidecar Pattern - Azure Architecture Center | Microsoft Learn · 2026-02-17
- Ambassador Pattern - Azure Architecture Center | Microsoft Learn · 2026-02-25
- Gatekeeper Pattern - Azure Architecture Center | Microsoft Learn · 2026-06-01
- The Istio service mesh