# Designing Connection Authentication for C1 Bridge

> Source: <https://www.c1.ai/engineering/designing-connection-authentication-for-c1-bridge>
> Published: 2026-08-11 13:00:00+00:00

C1 Bridge extends C1's AI Access Management features to MCP servers running inside private networks, without exposing those servers to the public internet. The [launch post](/blog/introducing-c1-bridge/) describes the product well, but in short, Bridge is designed to be as lightweight and easy to operate as possible, with minimal configuration required. It's infrastructure in a distributed system. Like all infrastructure, you probably won't notice it if C1 does its job well.

That doesn't make it simple internally. As one software pioneer famously put:

A distributed system is one in which the failure of a computer you didn't even know existed can render your own computer unusable. — Leslie Lamport, 1987 email, via

[Microsoft Research]

A failure of a computer that you didn't even know existed tends to be something that gets you in trouble. Here, I want to shine a light on one technical challenge we solved while building Bridge: connection re-authentication and revocation.

Security is a hard problem™. In Bridge, we maintain long-lived TLS connections to software running on-prem. There are several reasonable ways to authenticate those connections initially but nothing blog-worthy. The hard question is what comes next. A Bridge connection crosses a potentially sensitive trust boundary. Asking the on-prem Bridge client to periodically re-authenticate is prudent and gives customers a way to revoke their trust in a Bridge at any time, for any reason. That trust must not be a static concept.

## Connection (re)authentication: mTLS?[#](#connection-reauthentication-mtls)

Initially, we thought about using mTLS for connection authentication. The potential setup at least sounds simple enough: our on-prem Bridge client would acquire a certificate from C1 during setup, then present it to establish a connection.

The certificate check itself is not a huge problem. In fact, certificates are made to be checked cheaply and quickly, and their expiration fields give us dynamic control over the deadline for re-authentication. This is a great property when dealing with MCP servers: because traffic can be very intermittent, doing authentication at low-traffic times is a special kind of blessing.

For connection re-authentication, however, we still need a reliable way to force that repeated certificate check and tear down a connection that does not complete one. This is not fatal, but this does complicate the design.

Alas, certificates suffer from other issues. Certificate revocation is, let's say, [fraught with peril](https://letsencrypt.org/2022/09/07/new-life-for-crls). To readers whose eyes have already widened thinking about certificate revocation lists - I hear you, don't worry! We can mitigate some of this complexity by minting short-lived certificates. Then, with a well-synchronized clock, expiration bounds the revocation window for us.

Unfortunately, this trades away most of the flexibility about when to re-authenticate, which is what made this solution attractive in the first place. When a shorter certificate lifetime makes the system safer but more fragile, we've arrived at an undesirable trade-off between security and functionality. This is suddenly not as easy as we thought.

## Tokens, Tokens Everywhere[#](#tokens-tokens-everywhere)

After ruling out mTLS, we next considered tokens. There are a variety of common flavors of tokens on the web, and several could fit our purpose: opaque bearer tokens, JWTs, or even macaroons if we wanted to encode finer-grained authorization. C1 already uses token-based authentication for a variety of purposes, so this approach also benefits from being relatively easy to implement.

Alas, the re-authentication question is not really answered by choosing tokens. One conceptually simple approach would be to send a token with every request on the bridge: without a valid token, the request is rejected. This might look like:

```
[token][request A]   [token][request B]   [token][request C]
   |                    |                    |
validate             validate             validate
   |                    |                    |
 work                 work                 work
```

This gives tight control over what is sent over the bridge, at the wire cost of a token riding along with every data frame. While that may not feel expensive, tokens (like JWTs) can easily be hundreds of bytes long. These tokens are also often only minimally compressible. For small MCP tool calls, this can dramatically amplify the cost of making a request.

Another issue is conflating authentication with the ongoing data path. If a request is paired with a token, validation of that token must happen before the request is processed. This can also add work that must be performed before processing other requests. Since the bridge runs over TCP, bytes are delivered in order, so adding a token just adds overhead to every request.

## Keepalives were right there![#](#keepalives-were-right-there)

In life, sometimes you really are limited to picking between unsatisfying options. Anyone who has dealt with time zones knows this well. In other situations, you might just need to expand your imagination. What we really wanted was the ability to have quick, time-bounded authentication decisions at intervals of our choosing, with minimal disruption to Bridge traffic. An implementation using tried-and-tested protocols would be a huge bonus. Was this kind of solution really so different from what we had considered?

This was a moment where a visualization helped. Lots of network infrastructure uses timers to decide liveness. Load balancers, NATs, and firewalls all embrace some version of the "use it or lose it" paradigm. TCP connections can technically be idle, but in practice, traffic must flow across connections with some regularity, or they will typically get closed somewhere. In Bridge's case, we were already using short keepalive frames sent at configurable intervals to keep connections alive, so why not add a payload to those frames? This would look like:

```
[keepalive + token]              [keepalive + token]
        |                                |
     validate                         validate

        [request A] [request B] [request C]
             work       work       work
```

This felt like a breakthrough. Adding a payload to existing keepalive frames gave us an easy channel to re-authenticate whenever we wanted. If we re-authenticated frequently enough, we could tolerate occasionally losing keepalives without confusing silence for continued trust. Keeping authentication frames separate from data frames would let us process authentication payloads off the critical request path. It also clarified the payload itself: suddenly, token size matters less, because large tokens attached to tiny keepalive frames only impose periodic overhead. Even better, credential revocation is straightforward in this scheme, with a natural moment to terminate a revoked client.

Like short-lived certificates, this approach tightly bounds the window before re-authentication happens, but with a crucial difference in where that logic lives. With a certificate, expiration is a property of the credential itself, so an already-established connection does not have a natural point to force re-authentication other than tear-down and reconnect. With tokens on keepalives, C1 can make an online decision upon receipt of any keepalive frame, or timeout when waiting for one. This massively streamlines the whole process.

## What we learned, and closing thoughts[#](#what-we-learned-and-closing-thoughts)

By using keepalives, we improved usability and performance while providing ourselves a clear mechanism for re-authentication. Of course, that is not the end of the story; with security, you're never done. We still had to think through credential rotation and other implications of long-lived connections, not to mention the challenge of deploying Bridge across multiple clusters. There are also exciting lines of structural improvement to think about; a favorite of mine is QUIC support for better connection multiplexing.

This was a fun security design problem to work through, and one of several we tackled to launch Bridge. While using agents during testing and debugging is routine, leveraging them during the design process really helped challenge our decisions and expand our thinking. Having robots constantly ask questions during brainstorming, while sometimes annoying, felt like a real step forward, and also helped us survey common tunnel designs and learn from their deployments. To be clear, agents do not make the design decisions here, but they do make it easier to do design analysis. This extra analysis was crucial to feeling confident about Bridge, and is something I wonder how we ever lived without.

Infrastructure may never be easy or sexy, but it can be elegant. C1 Bridge is a simple abstraction, but it is not just `memcpy()`

over the network. Prioritizing customer trust means knowing when to fail closed.
