By John Andersen and Vinayak Baranwal
In this part we’ll be deploying our OAuth application which implements workload identity for Droplets and serves as a workload-identity-aware API proxy for the DigitalOcean API. DigitalOcean App Platform will be used to deploy the application. The reverse proxy, TLS certificates, and a database will be deployed by clicking the “Deploy to DigitalOcean” button below. Please consult the previous part of this series on OAuth App Based Workload Identity for Droplets for architectural details of the application and workload identity flow.
This part deploys the OAuth PoC on App Platform and wires RBAC + token exchange so Droplets don’t need static credentials.
OAuth workload identity federation uses fine-grained scopes to control Droplet and API access. This ensures that each workload receives only the minimum permissions required.
Our DigitalOcean OAuth Scopes documentation details all fine-grained scopes. These scopes are available within custom Role-Based Access Control (RBAC), OAuth Apps, and Personal Access Tokens (PATs). When the user is redirected by our root handler /
, they are presented with the DigitalOcean OAuth form which asks them to select which team they would like to grant the specific permissions defined by the scopes. We request only the scopes required to support our wrapped route handlers.
The proxy app’s auth route is simply a script which implements the DigitalOcean OAuth Web Application Flow. A user authenticates to a team and the proxy application stores the OAuth team token. When allowed by custom HCL which defines this application’s RBAC configuration, the team token is used in place of the workload identity token for requests to the upstream DigitalOcean API.
We can safely deploy this application on the open internet accessible to Droplets it spins up due to its reliance on the DigitalOcean OAuth API which underpins the security of the solution. The only outbound connections made are to DigitalOcean’s API, OAuth API, and Droplets created within the team on first boot. Further details of the security of the solution are found within Part 1 of this series on the PoC’s architecture.
Click the “Deploy to DigitalOcean” button to deploy the PoC, then find the FQDN of your deployed application as we’ll be using it within the next steps.
Deployed application FQDN.
We must now register an OAuth application, copy your deployed application’s FQDN and use the following registration form to create the application.
https://<deployment-name>.ondigitalocean.app/auth/digitalocean/v1/callback
Note: When registering the OAuth application, this setup creates a confidential client (with a client secret) required for secure token exchanges in this PoC. The <deployment-name>
portion of the URL is generated by App Platform at deploy time and forms part of your application’s unique FQDN.
Security Considerations: This PoC uses a confidential OAuth client implementing the Authorization Code Flow (client secret, no PKCE). Tokens are short-lived (≈5–15 minutes) and automatically rotated to minimize exposure.
Now we need to view the OAuth application’s Client ID and Client Secret so that we can set those values in our application’s environment variables.
Click View on OAuth application
OAuth application Client ID and Client Secret
Return to your App Platform application’s Settings page, open the App-Level Environment Variables section and populate the DIGITALOCEAN_OAUTH_CLIENT_ID
and DIGITALOCEAN_OAUTH_CLIENT_SECRET
environment variables setting them to the values we just viewed. Make sure to check the Encrypt box next to them. Reference How to Use Environment Variables in App Platform for more details.
Security Considerations: This PoC uses a confidential OAuth client implementing the Authorization Code Flow (client secret, no PKCE). Tokens are short-lived (≈5–15 minutes) and automatically rotated to minimize exposure.
App Platform environment variables.
Click Save, then ensure the app is re-deployed with the new environment variables by clicking the “Force rebuild and deploy” button.
Click the “Force rebuild and deploy” button.
Once rebuild and deploy finishes, navigate to your deployed application and complete the OAuth flow to authorize the application for one of your teams, make sure it’s the same team you’ve configured doctl to access using a Personal Access Token. See How to Install and Configure doctl for more details.
curl -s https://<deployment-name>.ondigitalocean.app/health
Expect 200 OK
. If not, re-check env vars and OAuth app configuration.
Click the “Live App” button.
To enable secure workload-based access control, define HCL policies and roles that map OAuth scopes to fine-grained RBAC permissions.
We need to define policies and roles for our API proxy to evaluate access control. We chose the HCL policy file format as its path-based syntax fits our authorization needs well for this use case.
We’ll store these roles and policies in a Git repository.
mkdir rbac
cd rbac
git init
It’s best practice to exchange the long-lived workload identity for short-lived access tokens to specific sets of resources. As such, we’ll first create a policy enabling this exchange. The ex-database-and-spaces-keys-access
policy allows the workload to exchange its workload identity token for a token with the database-and-spaces-keys-access
role with a short lifetime of 5 minutes (300 seconds). Token exchange is a best practice workload identity access pattern. See PyPI Docs: Internals and Technical Details for another example of token exchange.
policies/ex-database-and-spaces-keys-access.hcl
path "/v1/oidc/issue" {
capabilities = ["create"]
allowed_parameters = {
"aud" = "api://DigitalOcean?actx={actx}"
"sub" = "actx:{actx}:role:database-and-spaces-keys-access"
"ttl" = 300
}
}
On policy push the Auth Context {actx}
will be replaced with the UUID of the team token used to push to policy. The PoC ensures that actx
is always present in the subject and always set to the value from the audience. This ensures that we can reliably switch between authentication contexts via subject validation. We assign the policy to the ex-database-and-spaces-keys-access
role which we define as follows:
droplet-roles/ex-database-and-spaces-keys-access.hcl
role "ex-database-and-spaces-keys-access" {
aud = "api://DigitalOcean?actx={actx}"
sub = "actx:{actx}:role:ex-database-and-spaces-keys-access"
policies = ["ex-database-and-spaces-keys-access"]
}
The PoC supports reading information about Databases and creation of Spaces keys.
A policy which enables Database credential read can be written as follows.
policies/database-credential-read.hcl
path "/v2/databases/9cc10173-e9ea-4176-9dbc-a4cee4c4ff30" {
# Enable read of a single database by UUID.
capabilities = ["read"]
}
path "/v2/databases" {
# Enable read of databases tagged with given tag name.
# ? is used for query parameters.
capabilities = ["read"]
allowed_parameters = {
"?" = {
"tag_name" = "my-tag"
}
}
}
A policy which enables Spaces key creation can be written as follows.
policies/spaces-keys.hcl
path "/v2/spaces/keys" {
# Enable creation of Spaces keys to access a named bucket.
# Allowed parameters not in the top level ? key are treated
# as POST body keys whose values must match hcl defined values.
capabilities = ["create"]
allowed_parameters = {
"name" = "bucket-111-read-token-*"
"grants" = [
{
"bucket" = "111"
"permission" = "read"
}
]
}
}
path "/v2/spaces/keys/*" {
# Enable deletion of Spaces keys.
capabilities = ["delete"]
}
Note: The bucket identifier ("111"
) is a placeholder. If the bucket name or ID changes within the same team, access remains valid because Spaces buckets don’t have globally unique IDs. Replace "111"
with your team’s specific bucket reference.
We assign the policies to the database-and-spaces-keys-access
role, which we define as follows:
roles/database-and-spaces-keys-access.hcl
role "database-and-spaces-keys-access" {
aud = "api://DigitalOcean?actx={actx}"
sub = "actx:{actx}:role:database-and-spaces-keys-access"
policies = ["database-credential-read", "spaces-keys"]
}
Finally, we commit and push the configuration to the proxy app. Authentication is done via a custom git credential helper script which uses our doctl token to help the API server know what team we’re configuring roles and policies for. The DigitalOcean API token used must have permission to create Droplets within the team, as this PoC has a tight coupling of Droplet creation and usage of defined roles and policies.
# Define the FQDN of your deployed API proxy
export THIS_ENDPOINT="https://<deployment-name>.ondigitalocean.app"
mkdir -p "scripts/"
tee "scripts/git-credential.sh" <<'EOF'
#!/usr/bin/env bash
TOKEN=$(doctl auth token)
while IFS='=' read -r key value; do
if [[ -n "$key" && -n "$value" ]]; then
if [[ "$key" == "protocol" || "$key" == "host" ]]; then
echo "$key=$value"
fi
fi
done
echo "username=token"
# https://git-scm.com/docs/git-credential documents how this style of
# script works, stdin / stdout is used for communication to / from git
# and the bash process executing this script. Since we always use the
# doctl local PAT for authentication to this PoC deployment, we don't need
# to add custom logic around if this host or if this protocol, we always
# use the token for the deployed FQDN (git config --global
# credential."${THIS_ENDPOINT}".helper)
echo "password=${TOKEN}"
EOF
chmod 700 scripts/git-credential.sh
git init
git config --global credential."${THIS_ENDPOINT}".helper \
'!'"${PWD}/scripts/git-credential.sh"
git branch -M main
git add .
git commit -sm "feat: configure access from droplet"
git remote add deploy "${THIS_ENDPOINT}"
git push -u deploy main
# View deployed config
git fetch --all && git show deploy/schema:rbac.json | jq
Author’s Note: This proof-of-concept was developed in collaboration with DigitalOcean’s Product Security team to demonstrate secure OAuth 2.0 workload identity patterns in production environments.
Workload identity federation allows cloud resources like Droplets to authenticate securely using short-lived tokens instead of long-lived static credentials.
OAuth and OIDC eliminate static credentials by exchanging them for ephemeral tokens tied to workload identity, reducing attack surfaces and unauthorized access.
Yes. This model can be extended to workloads on App Platform or DigitalOcean Kubernetes using the same identity exchange and token validation framework.
{actx}
substitution.We can now begin creating Droplets with workload identity tokens! In our next entry, we’ll exchange our token for Database connection credentials as well as a Spaces bucket access key. Finally, we’ll add a role and policy to enable the same exchange to happen from a GitHub Actions workflow file within a repository of our choosing.
Read Part 1 on OAuth App Based Workload Identity for Droplet
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Security engineering and secure by-default support for engineering teams.
Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.