OAuth App Based Workload Identity for Droplets
Captured source
source ↗OAuth App Based Workload Identity for Droplets | DigitalOcean
© 2026 DigitalOcean, LLC. Sitemap .
Dark mode is coming soon. Trust & Security OAuth App Based Workload Identity for Droplets
By John Andersen
Senior Product Security Engineer
Updated: October 23, 2025 8 min read
.ondigitalocean.app" ,
"jwks_uri" : "https://.ondigitalocean.app/.well-known/jwks" ,
"response_types_supported" : [ "id_token" ] ,
"claims_supported" : [ "sub" , "aud" , "exp" , "iat" , "iss" ] ,
"id_token_signing_alg_values_supported" : [ "RS256" ] ,
"scopes_supported" : [ "openid" ] ,
}
/.well-known/jwks
{
"keys" : [
{
"kty" : "RSA" ,
"n" : "jplR2Q2_hJeA0tqAMqRppJxu16H8i8nrSgX..." ,
"e" : "AQAB" ,
"use" : "sig" ,
"kid" : "g1uTyq-nvRAVGYg6doHZ7LJVuNznJ1QX6OxVebUX6eE"
}
]
}
These well known URIs are what make workload identity federation possible. Federation in this context means distinct services are interoperable for the purposes of confirming the identity of a workload by services other than the issuer of the token. Later in our blog post, we’ll leverage this interoperability to verify validity of OIDC tokens issued to workflows run by GitHub Actions, in that case, the workflow is the workload. GitHub Action’s relevant well known URIs are as follows:
https://token.actions.githubusercontent.com/.well-known/openid-configuration
https://token.actions.githubusercontent.com/.well-known/jwks
The proof of concept we’ll be deploying will have the same well known paths, just with a different issuer domain name.
JWTs are issued with a set of “claims”. When a token is issued with a certain claim, the issuer is asserting that the token lays claim to some value, and by signing the token using its private key, the issuer is making an attestation to the validity of that claim. Therefore, if we trust a given issuer and an OIDC token passes cryptographic signature verification, we know we can trust the claims within that token. The set of claims we’re interested in for our PoC and that our service will issue tokens with are as follows:
{
"iss" : "https://.ondigitalocean.app" ,
"aud" : "api://DigitalOcean?actx=f81d4fae-7dec-11d0-a765-00a0c91e6bf6" ,
"sub" : "actx:f81d4fae-7dec-11d0-a765-00a0c91e6bf6:role:data-readwrite" ,
"droplet_id" : 514729608
}
Token claims
The issuer here is within the ”iss” claim, we append ”/.well-known/openid-configuration” to find the OpenID Configuration JSON, decode it, and subsequently find the public JSON Web Keys we can use to perform cryptographic signature verification of the token.
Also of critical importance to the security of workload identity federation are the audience ( ”aud” ) and subject ( ”sub” ) claims. The audience is used by the resource server to determine if the token is valid for its resources. Our audience is the API we’re authenticating to ( ”DigitalOcean” ). The subject varies depending on the workload environment we’re authenticating from. The subject identifies the workload itself, what is it?
In our PoC, setting the subject via Droplet tags is a critical piece of our trust flow. Creation of a Droplet with certain tags in combination with our RBAC roles will define what that Droplet has access to. We’re allowing any team member / role with permission to create a Droplet to set its subject. As such, one must take this into consideration when considering RBAC definitions and assignments . When we access resources from GitHub Actions, we know the answer to “what is it?” based on how the GitHub Actions orchestrator formats the subject: ”org/repo/.github/workflows/name.yml” .
Workload identity solution architecture
The goal for our PoC is to allow users to easily provision Droplet access to team resources such as Spaces Keys and Databases, removing the need for out-of-band secret provisioning or hard-coding sensitive values into cloud-init. In our example, we will use GitHub Actions workloads to deploy DigitalOcean Droplets configured with access to a Managed Database and a Spaces bucket without hard-coding any secret token.
Our PoC has the following aspects:
An OAuth Client Application following the DigitalOcean OAuth Web Application Flow
A policy-based evaluation to determine access control (supporting policy upload)
Provisioning and issuing workload identity tokens
.well-known URIs for OIDC token validation
Handlers to intercept and wrap routes provided by the DigitalOcean API:
Create a New Droplet
Retrieve an Existing Database Cluster
Create a New Spaces Access Key
To tie these aspects together and make them accessible to both our end-users and services such as Droplets and GitHub Actions which will access them, the PoC leverages caddy as a reverse proxy of DigitalOcean’s API. We can then write a Caddyfile configuring caddy to expose the OAuth route to the user, passthroughs of the DigitalOcean API to the user and workloads, and our wrappers around routes we’ll be modifying to enable workload identity.
Solution High Level Flow Caddy enables us to easily define routes we want to handle and routes we want to wrap. Our app-specific auth code is the callback.py file which redirects users when they land at our servers root to the DigitalOcean OAuth team selection and approval page. Once approved, callback.py handles secure storage of the users token for the selected team.
Part 1: Initial OAuth Web Flow for Team Level Token When the user issues a Droplet create request, the proxy application intercepts it and calls our wrapper. The wrapper creates a provisioning token which is a JWT with the subject containing a nonce value and the audience containing the team UUID associated with the DigitalOcean Personal Access Token sent to the Droplet create call endpoint. The wrapper then injects the provisioning token into the Droplet via modification of the user_data cloud-init field. The modified create request and PAT are then passed through to the upstream API which creates the Droplet.
Part 2: Workload Identity Token Provisioning for Droplet The application adds the workload identity token to the Droplet after successful completion of signing and validation of a provisioning token done via cloud-init . On first boot cloud-init executes the user_data which binds the provisioning token to the Droplet by signing it with the Droplet’s SSHD private key.
The provisioning token and signature are then exchanged for a workload identity token. Our service looks up the Droplet’s IP address using the team OAuth token received from the OAuth…
Excerpt shown — open the source for the full document.
Notability
notability 4.0/10Cloud provider feature update