Skip to main content

Get tokens for external services

This guide shows how to give your workloads short-lived access tokens for a third-party service, such as a SaaS product, without giving any workload that service's long-lived credentials. Use it when a service outside your trust domain accepts only its own OAuth client credentials, rather than SPIFFE identities.

How it works​

You register Defakto with the third-party service once. To authenticate to the service's authorization server, Defakto uses either a client secret that only your Trust Domain Servers can read, or a JWT that Defakto signs. When a workload needs to call the service, it asks Defakto for a token with an OAuth token exchange request, authenticating with its JWT-SVID or X.509-SVID. Defakto requests an access token from the service's authorization server using the OAuth client credentials grant, and returns that token to the workload unchanged. The workload then calls the service's APIs with it.

Workloads never see Defakto's credentials for the service, and each workload holds only a short-lived token.

In your environment, your workload asks the Defakto OAuth server for a token for the third-party service. Defakto gets a token from the service's authorization server with its own registration at the service, and returns it to the workload. The workload, which holds no secret for the service, calls the service's API with the service's token.

Before you begin​

Setting this up has two parts:

  1. Connect the service to your trust domain. Your Defakto administrators do this once for each service.
  2. Get tokens from a workload. The owner of each workload that calls the service does this.

Example values​

This page uses these example values. Replace them with your own as you go.

ValueExample
The address where you host your Trust Domain Servershttps://td-server.defakto.example.com
The third-party service, as workloads name ithttps://api.example.net
The service's authorization server (its issuer URL)https://auth.example.net
The scopes Defakto may request from the servicerecords.read, records.write
The name you give the service in the configurationvendor-api

Part 1: Connect the service to your trust domain​

Your Defakto administrators do this once for each service. After that, the workloads that your token policy allows can get tokens for the service. If the service is already connected, skip to Part 2.

You need:

  • Permission to register an OAuth client with the third-party service
  • If Defakto will authenticate with a client secret, permission to create Kubernetes Secrets in the namespace where your Trust Domain Servers run
  • Permission to change your trust domain's Managed Configuration

Step 1: Register Defakto with the service​

Following the service provider's instructions, create an OAuth client for Defakto that uses the client credentials grant. Some providers call this a service account, a machine-to-machine application, or an API client. Grant it the scopes your workloads need, and record its client ID. Then choose how Defakto authenticates to it:

  • A client secret. Record the client's secret, and continue with step 2.
  • A JWT that Defakto signs (private_key_jwt), if the service's authorization server supports it. In the client's authentication settings, add your trust domain's OAuth JWKS Endpoint as its JWKS URL, so the authorization server can verify Defakto's JWTs and pick up new keys when Defakto rotates them. Find the OAuth JWKS Endpoint in the Defakto console, under your trust domain's Settings > Trust domain URLs, or in the output of defakto trust-domain info <trust-domain>. No secret is stored, so skip step 2.

Step 2 (client secret only): Store the client secret​

Create a Kubernetes Secret that holds the client secret, in the namespace where your Trust Domain Servers run. You create and manage the Secret yourself, the same way you manage your other Kubernetes Secrets. If you run several Trust Domain Server deployments, create the Secret for each one. Trust Domain Servers pick up a changed secret without a restart.

vendor-api-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: vendor-api
namespace: <Trust Domain Server namespace>
labels:
defakto.security/credential: oauth-server
annotations:
defakto.security/issuer: https://auth.example.net
type: Opaque
stringData:
clientSecret: <client-secret-from-step-1>
  • The defakto.security/credential label marks the Secret as one the OAuth server may read.
  • The defakto.security/issuer annotation names the service's authorization server. Defakto uses the secret only with that authorization server.

Then let your Trust Domain Servers read the Secret. In your Helm values, turn on oauthClientSecrets, and list the Secret's name:

trustDomainDeployment:
deployment:
oauthClientSecrets:
enabled: true
names: [vendor-api]

Then upgrade your Helm release. List the name of every Secret that holds a client secret. Without names, Trust Domain Servers can read every Secret in their namespace.

Step 3: Configure the OAuthServer section​

Add the service to your trust domain's OAuthServer configuration, creating the configuration if the trust domain doesn't have one yet:

oauth-server.yaml
section: OAuthServer
schema: v1
spec:
endpoints:
publicBaseURL: https://td-server.defakto.example.com

externalResources:
- name: vendor-api
resource: https://api.example.net
authorizationServer:
issuer: https://auth.example.net
grant:
clientCredentials:
clientID: <client-id-from-step-1>
clientAuth:
method: client_secret_basic
secretRef:
name: vendor-api
key: clientSecret
scopes: [records.read, records.write]
  • publicBaseURL is the address where you host your Trust Domain Servers. If Defakto hosts your Trust Domain Servers, leave it out.
  • resource is the value workloads name when they ask for a token for this service.
  • authorizationServer.issuer is the issuer URL of the service's authorization server. Defakto finds the authorization server's token endpoint in the OAuth or OpenID Connect discovery metadata published at this issuer, so the authorization server must publish that metadata.
  • clientID is the client ID from step 1.
  • clientAuth is how Defakto authenticates to the service. With a client secret, method is client_secret_basic, and secretRef names the Kubernetes Secret from step 2 and the key that holds the secret.
  • scopes are the scopes Defakto requests from the service. A workload can ask for fewer, and gets all of them if it asks for none.

If Defakto authenticates with a JWT that it signs, use this clientAuth instead, with no secretRef:

clientAuth:
method: private_key_jwt

Apply the configuration to your trust domain. You can apply Managed Configuration through the Defakto API, Terraform, or the Defakto CLI. See applying configuration. For example, with the CLI:

defakto config set trust-domain --id <trust-domain-id> oauth-server.yaml

Step 4: Allow workloads to get tokens​

Every workload gets tokens through Defakto's one registration at the service, so the service can't tell your workloads apart. Your token policy decides which workloads can get them. A workload gets a token for the service only when an allow or external authorizer rule matches its request, and the rule's match.audience is the service's resource. Without such a rule, every request for the service is refused.

For example, this policy lets only the reporting workloads get tokens for the service, with read-only scope, and refuses every other request:

spec:
# endpoints and externalResources as in step 3
policy:
authorization:
unmatched: deny
allow:
- name: reporting-reads-vendor-records
match:
caller:
spiffeID: spiffe://example.com/ns/reporting/**
requestedTokenType: urn:ietf:params:oauth:token-type:access_token
audience: https://api.example.net
token:
allowedScopes: [records.read]

See the token policy reference for every kind of rule.

Part 2: Get tokens from a workload​

The owner of each workload that calls the service does this.

You need:

  • Part 1 completed for the service
  • A workload that can get JWT-SVIDs or X.509-SVIDs, and can reach your Trust Domain Servers

Step 1: Request a token for the service​

Send a token exchange request to <address of your Trust Domain Servers>/oauth/token, naming the service as the audience:

The workload proves its identity with a JWT-SVID sent as a client assertion. Get the JWT-SVID the usual way, through the SPIFFE Workload API or, in serverless runtimes, the Defakto serverless SDK, with its audience set to your trust domain's JWT issuer, not its OAuth issuer. Find the JWT Issuer in the Defakto console, under your trust domain's Settings > Trust domain URLs, or ask your Defakto administrators.

POST /oauth/token HTTP/1.1
Host: td-server.defakto.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&audience=https://api.example.net
&scope=records.read
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe
&client_assertion=<the workload's JWT-SVID>

The body is shown on separate lines for readability. Send it as a single URL-encoded form body.

ParameterRequiredValue
grant_typeYesExactly urn:ietf:params:oauth:grant-type:token-exchange
audienceYesThe service's resource value from the configuration, such as https://api.example.net. You can send it as resource instead.
scopeNoThe scopes your workload needs, separated by spaces. Without it, Defakto requests all the scopes in the configuration.
client_assertion_type and client_assertionWith a JWT-SVIDurn:ietf:params:oauth:client-assertion-type:jwt-spiffe, and the workload's JWT-SVID
client_idWith an X.509-SVIDThe workload's SPIFFE ID, exactly as it appears in the X.509-SVID

The response contains the service's own access token, exactly as the service's authorization server issued it, in a standard OAuth token response:

{
"access_token": "<the service's access token>",
"token_type": "Bearer",
"expires_in": 3600
}

Step 2: Call the service​

Call the service's APIs with the access token, as the service's documentation describes, usually in the Authorization header. The token's lifetime is set by the service's authorization server. Reuse the token until shortly before expires_in elapses, then request a new one.

Troubleshooting​

tip

To see why a request failed, open your trust domain in the Defakto console, and go to Issuance > Token Exchanges.

The token endpoint returns invalid_client. Defakto couldn't authenticate the workload. With a JWT-SVID, check that its audience is the JWT issuer URL, not the OAuth issuer or the service's URL, and that it hasn't expired. With an X.509-SVID, check that client_id is exactly the SPIFFE ID in the workload's X.509-SVID.

The token endpoint returns invalid_scope. None of the scopes the workload asked for is in the service's scopes in the configuration and allowed by the token policy. When only some of them are, Defakto requests just those, without an error.

The token endpoint returns server_error. Defakto couldn't get a token from the service's authorization server. The OAuth server's audit event for the request records why:

  • secret_unusable: The Kubernetes Secret is missing from a Trust Domain Server's namespace, isn't labeled, or names a different issuer in its annotation. Or the Trust Domain Servers aren't allowed to read it, because oauthClientSecrets isn't enabled in your Helm values, or doesn't list the Secret.
  • provider_denied: The service's authorization server refused Defakto's request. For example, the client secret or client ID is wrong, the authorization server doesn't have Defakto's JWKS URL or doesn't accept signed JWTs from clients, the client doesn't allow the client credentials grant, or Defakto's registration at the service doesn't have the requested scopes.
  • upstream_unavailable: Defakto couldn't reach the service's authorization server, or it didn't respond.

The token endpoint refuses every request for the service. No allow or external authorizer rule matches the request with the service's resource in match.audience. Add a rule as in step 4.

The service rejects the token. Check the scopes the service requires for the call, and that Defakto's registration at the service has been granted them.

Choosing how many registrations to use​

You can register Defakto with the service once, and share that registration across every workload that's allowed to use it. Or you can create a separate registration for each workload, or for each group of workloads.

A shared registration is a reasonable default, and keeps setup light. A separate registration is worth the extra setup when:

  • The service's own logs and permissions need to tell your workloads apart
  • You want to limit the effect of one registration's credentials being compromised
  • The service requires a separate client for each caller

Either way, Defakto's audit events record which workload received each token.

To use separate registrations, add an externalResources entry for each one. Give each entry its own resource value, such as https://api.example.net/reporting and https://api.example.net/billing. The resource value is a name that workloads use to ask for a token, so it doesn't need to be the service's real address. Then add a token policy rule for each entry, so that each workload can request tokens only from its own entry.