Report this

What is the reason for this report?

OAuth Workload Identity Federation on DigitalOcean - Part 2

Published on October 23, 2025
OAuth Workload Identity Federation on DigitalOcean - Part 2

Part 2: Deployment and Configuration

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.

Key Takeaways

  • Learn how to deploy an OAuth-based workload identity PoC on DigitalOcean App Platform.
  • Understand how OAuth scopes map to RBAC for granular access control.
  • Configure environment variables for secure OAuth app deployment.
  • Create and push custom HCL policies and roles for token-based access.
  • Prepare to extend this setup for Droplets and GitHub Actions in Part 3.

This part deploys the OAuth PoC on App Platform and wires RBAC + token exchange so Droplets don’t need static credentials.

Fine-Grained OAuth Scopes

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.

Screenshot of the OAuth application scopes requested in the DigitalOcean authorization form.

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.

Deploy to DigitalOcean

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 shown on the App Platform dashboard.
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.

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.

OAuth application registration form in the DigitalOcean Control Panel.

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.

View button for the newly created OAuth application.
Click View on OAuth application

Client ID and Client Secret displayed in OAuth application details.
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 configuration screen.
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.
Force rebuild and deploy button on App Platform.
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.

Verify the OAuth proxy health

curl -s https://<deployment-name>.ondigitalocean.app/health

Expect 200 OK. If not, re-check env vars and OAuth app configuration.

Clicking the “Live App” button to open the deployed OAuth application.
Click the “Live App” button.

Policies and Roles

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.

How to create 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:

How to create 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.

How to create 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.

How to create 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:

How to create 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.

FAQs

What is workload identity federation?

Workload identity federation allows cloud resources like Droplets to authenticate securely using short-lived tokens instead of long-lived static credentials.

How does OAuth/OIDC improve Droplet security?

OAuth and OIDC eliminate static credentials by exchanging them for ephemeral tokens tied to workload identity, reducing attack surfaces and unauthorized access.

Can this extend to App Platform or Kubernetes?

Yes. This model can be extended to workloads on App Platform or DigitalOcean Kubernetes using the same identity exchange and token validation framework.

Troubleshooting

  • OAuth callback mismatch: Update the callback URL in your OAuth app.
  • 401 from proxy: Verify team authorization and requested scopes.
  • RBAC deny for Spaces keys: Confirm the policy push and {actx} substitution.

Next Steps

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

References

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

John Andersen
John Andersen
Author
Senior Product Security Engineer
See author profile

Security engineering and secure by-default support for engineering teams.

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

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.

Still looking for an answer?

Was this helpful?


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!

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.