
# Upstream OAuth 2.0 Client Credentials Auth Policy

Secure your origin server with OAuth 2.0 authentication by automatically adding
an `Authorization` header to upstream requests. This policy enables your Zuplo
gateway to authenticate with any OAuth 2.0 identity provider that supports the
client credentials grant — such as Auth0, Okta, Keycloak, Microsoft Entra ID, or
your own authorization server.

With this policy, you'll benefit from:

- **Enhanced Backend Security**: Restrict access to your origin servers to only
  your Zuplo gateway
- **Simplified Authentication**: Delegate authentication and authorization to
  your gateway without backend code changes
- **Automatic Token Management**: Handle token acquisition, caching, and renewal
  automatically
- **Provider Flexibility**: Works with any standards-compliant OAuth 2.0 token
  endpoint, no provider-specific policy required
- **Credential Security**: Store sensitive client credentials securely in your
  Zuplo environment

## Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

```json title="config/policies.json"
{
  "name": "my-upstream-oauth-client-credentials-inbound-policy",
  "policyType": "upstream-oauth-client-credentials-inbound",
  "handler": {
    "export": "UpstreamOAuthClientCredentialsInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "audience": "https://api.example.com",
      "clientId": "my-client-id",
      "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
      "credentialsIn": "body",
      "expirationOffsetSeconds": 300,
      "headerName": "Authorization",
      "headerScheme": "Bearer",
      "scope": "read:orders write:orders",
      "tokenRetries": 3,
      "tokenUrl": "https://your-tenant.us.auth0.com/oauth/token"
    }
  }
}
```

### Policy Configuration

- `name` <code className="text-green-600">&lt;string&gt;</code> - The name of your policy instance. This is used as a reference in your routes.
- `policyType` <code className="text-green-600">&lt;string&gt;</code> - The identifier of the policy. This is used by the Zuplo UI. Value should be `upstream-oauth-client-credentials-inbound`.
- `handler.export` <code className="text-green-600">&lt;string&gt;</code> - The name of the exported type. Value should be `UpstreamOAuthClientCredentialsInboundPolicy`.
- `handler.module` <code className="text-green-600">&lt;string&gt;</code> - The module containing the policy. Value should be `$import(@zuplo/runtime)`.
- `handler.options` <code className="text-green-600">&lt;object&gt;</code> - The options for this policy. [See Policy Options](#policy-options) below.

### Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

- `tokenUrl` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The URL of the OAuth 2.0 token endpoint that issues the access token.
- `clientId` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The client ID used to authenticate with the token endpoint.
- `clientSecret` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The client secret used to authenticate with the token endpoint.
- `scope` <code className="text-green-600">&lt;string&gt;</code> - Space-delimited list of scopes to request. When not set, the `scope` parameter is omitted from the token request.
- `audience` <code className="text-green-600">&lt;string&gt;</code> - The value of the `audience` form parameter sent to the token endpoint. Required by some identity providers such as Auth0. When not set, the parameter is omitted from the token request.
- `credentialsIn` <code className="text-green-600">&lt;string&gt;</code> - Where the client credentials are sent on the token request. `body` sends `client_id` and `client_secret` as form parameters. `header` sends them with HTTP Basic authentication as described in RFC 6749 section 2.3.1. Allowed values are `body`, `header`. Defaults to `"body"`.
- `additionalParameters` <code className="text-green-600">&lt;object&gt;</code> - Additional form parameters to include in the token request, for example `resource` for identity providers that require it.
- `headerName` <code className="text-green-600">&lt;string&gt;</code> - The name of the header on the upstream request that the access token is set on. Defaults to `"Authorization"`.
- `headerScheme` <code className="text-green-600">&lt;string&gt;</code> - The scheme that prefixes the access token in the header. When the token response includes a `token_type`, that value is used instead. Defaults to `"Bearer"`.
- `tokenRetries` <code className="text-green-600">&lt;number&gt;</code> - The number of times to retry fetching the token in the event of a failure. Defaults to `3`.
- `expirationOffsetSeconds` <code className="text-green-600">&lt;number&gt;</code> - The number of seconds less than the token expiration to cache the token. Defaults to `300`.

## Using the Policy

This policy authenticates your Zuplo gateway to OAuth 2.0-protected backend
services by automatically adding an access token to the `Authorization` header
(or a custom header) of upstream requests. It uses the OAuth 2.0 client
credentials grant against any token endpoint you configure, so it works with
Auth0, Okta, Keycloak, Microsoft Entra ID, and any other standards-compliant
identity provider.

### How It Works

The policy performs the following operations:

1. Requests an access token from the configured token endpoint using the client
   credentials grant
2. Caches the token for subsequent requests until it nears expiration
3. Adds the token to the configured header (default `Authorization`) using the
   `token_type` returned by the token endpoint, falling back to the configured
   `headerScheme` (default `Bearer`)
4. Automatically handles token renewal when needed

Tokens are cached for `expires_in - expirationOffsetSeconds` seconds. If the
token endpoint doesn't return an `expires_in` value, the policy caches the token
conservatively as if it expired after 10 minutes (5 minutes with the default
`expirationOffsetSeconds` of 300) rather than caching it indefinitely.

### Policy Configuration

Configure the policy with your identity provider's token endpoint and client
credentials:

```json
{
  "name": "upstream-oauth-client-credentials",
  "export": "UpstreamOAuthClientCredentialsInboundPolicy",
  "module": "$import(@zuplo/runtime)",
  "options": {
    "tokenUrl": "https://your-tenant.us.auth0.com/oauth/token",
    "clientId": "$env(OAUTH_CLIENT_ID)",
    "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
    "audience": "https://api.example.com",
    "scope": "read:orders write:orders"
  }
}
```

### Sending Client Credentials

By default (`credentialsIn: "body"`), the client credentials are sent as
`client_id` and `client_secret` form parameters in the token request body. While
[RFC 6749 section 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1)
recommends HTTP Basic authentication, `body` is the default because it's the
method most widely accepted across identity providers (including Auth0, Okta,
Entra ID, and Keycloak) and avoids credential-encoding differences between
providers' Basic authentication implementations.

Set `credentialsIn` to `header` to send the credentials with HTTP Basic
authentication instead:

```json
{
  "options": {
    "tokenUrl": "https://idp.example.com/oauth2/token",
    "clientId": "$env(OAUTH_CLIENT_ID)",
    "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
    "credentialsIn": "header"
  }
}
```

### Provider-Specific Parameters

Some identity providers require extra form parameters on the token request. Use
`additionalParameters` to send them, for example the `resource` parameter:

```json
{
  "options": {
    "tokenUrl": "https://idp.example.com/oauth2/token",
    "clientId": "$env(OAUTH_CLIENT_ID)",
    "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
    "additionalParameters": {
      "resource": "https://api.example.com"
    }
  }
}
```

The `grant_type` parameter is always `client_credentials` and the dedicated
`clientId`, `clientSecret`, `scope`, and `audience` options always take
precedence over entries in `additionalParameters`.

### Usage Example

Apply the policy to routes that need to call your OAuth-protected backend:

```json
{
  "paths": {
    "/api/orders": {
      "get": {
        "x-zuplo-route": {
          "policies": {
            "inbound": ["jwt-auth", "upstream-oauth-client-credentials"]
          },
          "handler": {
            "export": "forwardToOrigin",
            "module": "$import(@zuplo/runtime)",
            "options": {
              "baseUrl": "https://api.internal.example.com"
            }
          }
        }
      }
    }
  }
}
```

### Security Considerations

- Store the client secret as an environment variable using `$env(VARIABLE_NAME)`
  syntax
- Grant the OAuth client the minimum scopes required to access your backend
  services
- Regularly rotate your client secrets according to your security policies

Read more about [how policies work](/articles/policies)
