At Formal, we hate secrets. Since day one, we’ve been on a quest to drive the number of long-lived secrets in our infrastructure to zero.
We want our product and the way our customers interact with it to reflect that philosophy, too. Accordingly, we built OIDC Integrations a few months ago: a way for arbitrary machine workloads to authenticate to Formal on the strength of their own infrastructure identity, and optionally attest a human identity along the way.
Beyond product and CI/CD workloads, we’ve also found that supporting OIDC has been extremely useful for enabling agents to do more in our infrastructure. Cloud coding agents like Cursor and Devin themselves are OIDC issuers, which means that we can now give them auditable, policy-controlled, read-only access through Formal to our production data and cloud infrastructure.
OpenID Connect (OIDC)
For a brief refresher, an OIDC authentication flow works as follows for machine workloads. The goal is to obtain a short-lived token from the workload’s identity provider (IdP) and use it as proof of identity on an external service, known as the relying party (RP). The relying party is usually preconfigured with knowledge about the IdP.
- The workload obtains its OIDC JWT token from its environment. Sometimes this is provided as an environment variable (e.g., GitHub, GitLab); sometimes the workload needs to retrieve it by making an API call to a metadata endpoint (e.g., AWS, GCP, Azure). Typically, the token is scoped to the RP (e.g., by setting the Audience
audclaim) to prevent token reuse across different RPs. - The workload makes an API call to the RP, using the obtained OIDC token as a bearer token (i.e.,
Authorization: Bearer eyJ...). - The RP looks up the JWKS endpoint (usually something like
https://idp.com/.well-known/openid-configuration) from its configuration and obtains the IdP’s public key to verify the JWT with. - The RP verifies the JWT is signed with the IdP’s private key. The RP also checks some of the claims embedded in the JWT as an authentication condition — which is important, because IdPs are typically multi-tenant. These claims are also typically used to map the incoming identity to a machine identity on the RP.
- If all checks pass, the API call is allowed to proceed. Sometimes, additional authorization conditions are applied to API calls that are authenticated via OIDC.
OIDC at Formal
Our OIDC integrations follow the above flow, with a few key additional details:
- Customers can specify a CEL condition for each integration, where a given token is only allowed to authenticate if that condition evaluates to true. Additionally, OIDC claim fields are also surfaced as inputs to Permissions, such that customers can define more granular authorization conditions for specific control plane API requests.
- One endpoint that OIDC-authenticated callers can call is
CreateFederationToken, which returns a short-lived Connector access token that embeds the source OIDC claim fields. This means that workloads can use the Connector as a credential-injecting proxy without needing to store long-lived credentials themselves. - SPIFFE workloads work natively too: Formal verifies JWT-SVIDs from SPIRE like any other OIDC token, with the SPIFFE ID available to Policies and Permissions.
- OIDC can also be used to authenticate the Formal Endpoint and Kubernetes Egress Agent.
Attesting human identity
Every OIDC integration maps to a single machine user. This is a good default model for most traditional machine workloads; however, many workloads we care about don’t act on their own behalf. For example, CI jobs can usually be attributed to a human that pushed a commit, and cloud agents are often run because someone kicked one off from Slack. As such, each integration can optionally define a token claim that Formal resolves to a human identity which appears in logs and as input to policies.
The two identities involved here come with different kinds of trust. Formal can cryptographically authenticate the machine identity, but the human identity is only asserted by the downstream workload. We accept that claim because our customer trusts the system making it, and explicitly configures Formal to trust it too.
We’ve supported this shape since day one, but for a very different use case: many of our customers use Formal to control database traffic between business intelligence (BI) tools and their data sources. Tools like Metabase can only use one credential per database, but customers often differentially enforce controls based on the human user logged into Metabase. We originally built end-user identity propagation as a way to allow Metabase to assert the human identity to database queries and let Formal evaluate policies against it.
That pattern turns out to fit cloud coding agents almost perfectly, especially multiplayer ones that a whole team shares and anyone can launch from Slack, Linear, or GitHub. A cloud agent platform can’t reasonably produce a separate credential for every human who might launch an agent. It can, however, issue a signed token that says which agent session this is, which repository it’s working in, and who started it. Our customers already trust their agent platform to make that statement, just as they trust Metabase to report who is logged in. As a result, customers can use the same Formal primitive to both mask database columns in Metabase and to control cloud agent access to infrastructure by its owner’s team.
Dogfooding OIDC integrations
At Formal, we extensively dogfood our own product — we have a Connector that sits in front of our production databases, S3 buckets, and log clusters, and we use the Formal Endpoint to monitor and control coding agent usage on developer laptops. Our production tenant’s control plane is fully managed with Terraform, so one of our first internal use cases for the OIDC integration was to switch the way Terraform Cloud authenticates to the Formal control plane to use OIDC rather than a long-lived API key.
We’re heavy customers of a few cloud coding agent platforms, and we’ve been gradually shifting development away from our laptops and onto cloud sandboxes. We’d been reluctant to give these cloud agents access into our infrastructure, as doing so would have meant handing out static credentials without much in the way of granular scoping.
However, OIDC presented the solution — we no longer needed to assign coding agents static credentials, and we could evaluate policies based on specific details of the cloud agent (such as which human kicked it off, or which GitHub repo it’s working on).
The following is a non-exhaustive list of the use cases we’ve found to make our cloud agents more productive.
Product analytics
Often, product analytics workflows lean on data from our production databases (which we replicate to an analytics database). For example, measuring the rate of adoption of a new product object requires seeing which customers actually have database entries for that product object.
In the not-too-distant past, this process used to be manual — engineers used to need to manually compose SQL queries for these workflows. Coding agents are great both at writing SQL and digesting the results, but we previously didn’t have a safe way to give them controlled access. As a result, we would have to manually run the query, export the result as CSV, and then paste it back into the agent’s context.
With OIDC in the mix, however, we now had a way to treat each cloud agent session as its own identity. Inspired by a flow we helped our friends at Cursor create, we set up a way for cloud agents to request approval from their human owners for short-lived access to our production databases. This flow uses our OIDC integration, along with a few other existing platform primitives including Workflows and Policy Suspensions.
In particular, the agent can tell the human over Slack why exactly it needs access, and for how long. Once the human approves, access is scoped to the cloud agent session ID and the specific database that was requested.
Debugging asynchronous workflows
Our workflow orchestration provider doesn’t supply a remote MCP server, so previously there was no way to give cloud agents access without long-lived credentials. This limited our ability to use coding agents to optimize workflow performance based on real run history.
We can register the workflow orchestrator as a Formal resource and store a read-only API key as a Native User. Using OIDC, cloud agents can request and be approved for short-lived access as they would with databases above. Requests are always logged in Formal and attributed to both the cloud agent and the engineer that launched it.
Testing cloud-native features
The Formal platform supports a number of native features of AWS, GCP, and Azure. For example, the Connector can authenticate to databases and Kubernetes clusters using IAM. Additionally, the control plane can integrate with each cloud provider to autodiscover resources and to send logs to storage buckets.
When we iterate on these features, we can now have cloud agents federate using OIDC to both our Formal dev tenant and to a dev account on a cloud provider. This allows an agent to ensure its code change works as intended in a live, representative cloud environment, helping it close the loop without needing to resort to brittle mocks.
For example, I recently had a Cursor cloud agent work on extending our Kubernetes operator to authenticate to the Formal control plane using OIDC (e.g., with Azure Workload Identity). During the development process, I gave the cloud agent access to a dev Azure account, into which it deployed a new AKS cluster to run the Kubernetes operator from its PR branch. The agent was able to verify through connecting to our Formal dev tenant that once it created a FormalPolicy CRD in the cluster, the policy was synced to that dev tenant — proving the code change worked as intended.
Conclusion
Cloud agents become substantially more useful when they can close the loop themselves: they should be able to gather product usage data, investigate failures, and validate their work in realistic environments. However, that shouldn’t need to require handing out reusable, long-lived credentials.
OIDC helps us model an individual agent run as a unique workload identity. When an agent makes requests to external services via Formal, access can be tied to the engineer who launched it, the repository it is working on, and the specific agent session making the request. Production access remains read-only and approval-gated, while permissions in development environments can be broader without becoming permanent.
The broader lesson from our internal use is that agent access should be modeled as a temporary delegation — not just as another machine workload with another long-lived secret. Once that distinction exists, giving agents access to real infrastructure becomes much less frightening and considerably more useful.