# Vault Proxy

Vault Proxy forwards a request from your backend to a third-party service, such as a payment gateway, and fills in vaulted data on the way, so sensitive data such as PII and PCI data never touches your application code or infrastructure in plaintext. Keeping that data off your network also helps you meet compliance requirements such as PCI.

Call the proxy from your backend with your secret API key; the [API
authentication guide](/api-reference#authentication) covers how.

Vault Proxy supports:

| Feature                    | Overview                                                                                                         |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Any HTTPS URL and method   | Works with any HTTPS resource                                                                                    |
| Flexible detokenization    | Detokenizes any type of request body, whatever the content type                                                  |
| Fixed IP range             | Requests from Vault Proxy always come from a fixed set of IP addresses that you can allowlist at the destination |
| Custom headers             | Attaches custom headers                                                                                          |
| Authentication secrets     | Attaches custom authentication secrets                                                                           |
| Client certificates (mTLS) | Attaches a certificate and key for mTLS client certificate authentication                                        |
| Server certificate pinning | Validates the destination against one or more root CAs and leaf certificates you specify                         |
| Ingress vaulting           | Vaults data that comes back in the response, with JSONPath rules (XPath and regex are planned)                   |

## Configuration

You configure the proxy in one of two ways: save a configuration in the dashboard or through the admin API, or pass every setting as headers on each request ("just-in-time"). Both support the same capabilities. A saved configuration also holds the destination's authentication credentials, so you do not manage them in your requests.

### Just-in-time

To proxy to any destination without a saved configuration, send this header:

| Header                  | Usage                     | Description                                   |
| ----------------------- | ------------------------- | --------------------------------------------- |
| `x-fp-proxy-target-url` | Required for just-in-time | The HTTPS URL Footprint routes the request to |

### By configuration

To use a configuration you saved in the dashboard or through the API, put its `id` in the request path:

```bash
curl  https://api.onefootprint.com/vault_proxy/proxy_id_AY5ec7I5QDUFWG8BAQG78k \
  ...other parameters...
```

## Basics

### Making a proxy request

A Vault Proxy request is an authenticated HTTP `POST` to `/vault_proxy/jit` or `/vault_proxy/{proxy_config_id}`.

For example, this request fills in one user's vaulted data and forwards the body to a payment API:

```bash

curl https://api.onefootprint.com/vault_proxy/jit \
    -u sk_test_CXUsbCR8j2kH6e5GeEl8eSBnQTIPCUaKpv: \
    -X POST \
    -H 'x-fp-proxy-target-url: https://payments.acmebank.com' \
    -H 'x-fp-proxy-fwd-custom-header: custom value' \
    -H 'x-fp-proxy-fwd-content-type: application/json' \
    --data '{
        "full_name": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.first_name }} {{ fp_id_tctecBEvGc98V7Vx4MhZU.id.last_name }}",
        "last4_ssn": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.ssn4 }}",
        "cc": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card }}",
        "cc_exp": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card_exp }}",
        "cc_cvc": "{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card_cvc }}"
    }'
```

Footprint detokenizes each field in the body inside the vault enclave, then sends the updated plaintext body to the target (here `https://payments.acmebank.com`) with the headers `'Custom-Header: custom value'` and `'Content-type: application/json'`.

Control headers change how the upstream request behaves:

| Header                     | Required? | Function                                                                                                     |
| -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
| `x-fp-proxy-fwd-<HEADER>`  | Optional  | Sends a header named `<HEADER>` with the corresponding value                                                 |
| `x-fp-proxy-method`        | Optional  | Selects the HTTP method (defaults to `POST`)                                                                 |
| `x-fp-proxy-access-reason` | Optional  | The decryption reason recorded in the access and security logs during detokenization. Defaults to no reason. |

The request fails if the API key lacks permission to detokenize any of the
supplied fields, or if any supplied field does not exist in the vault.

The body does not need to be JSON. Footprint substitutes tokens in place in any UTF-8 body;
the [token body template format](#basics-token-body-template-format) describes how to write them.

### Token body template format

Vault Proxy replaces every token in the request body with the decrypted plaintext value. A token starts with `{{` and ends with `}}`, and you can write it in one of two forms.

**Fully-qualified tokens.** Write `{{ <footprint_user_token>.<data_identifier> }}`, where `<footprint_user_token>` is the user's `fp_id` and `<data_identifier>` is any identity (KYC) field, prefixed `id.`, or any custom field you defined, prefixed `custom.`.

| Fully-qualified format                                 |
| ------------------------------------------------------ |
| `{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.last_name }}`       |
| `{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.ssn9 }}`            |
| `{{ fp_id_tctecBEvGc98V7Vx4MhZU.id.dob }}`             |
| `{{ fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card }}` |

**Inferred-user tokens.** When every token refers to one user's vault, name that user in a header instead:

| Header    | Value                                       |
| --------- | ------------------------------------------- |
| `x-fp-id` | The `fp_id` of the user the tokens refer to |

Each token then omits the `<footprint_user_token>` part and becomes `{{ <data_identifier> }}`.

| Inferred-user format       |
| -------------------------- |
| `{{ id.last_name }}`       |
| `{{ id.ssn9 }}`            |
| `{{ id.dob }}`             |
| `{{ custom.credit_card }}` |

### Filter functions

Filter functions transform a value on its way into or out of the vault, as in template languages such as Jinja and Handlebars. Append zero or more filters to a token, separated by `|`.

| Filter function                      | Description                                                                                                                                                                                                                       |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `to_lowercase`                       | Converts the target UTF-8 string to lowercase                                                                                                                                                                                     |
| `to_uppercase`                       | Converts the target UTF-8 string to uppercase                                                                                                                                                                                     |
| `to_ascii`                           | Converts the target UTF-8 string to ASCII characters only                                                                                                                                                                         |
| `prefix(n)`                          | Returns the first `n` characters of the target UTF-8 string, where `n` is a positive integer                                                                                                                                      |
| `suffix(n)`                          | Returns the last `n` characters of the target UTF-8 string, where `n` is a positive integer                                                                                                                                       |
| `replace(from,to)`                   | Replaces every match of the string `from` with the string `to` in the target UTF-8 string                                                                                                                                         |
| `date_format(from_format,to_format)` | Parses the target string in date format `from_format` and converts it to date format `to_format`. Supports [`strftime` format strings](https://strftime.org). Errors if the target string cannot be coded in the supplied formats |
| `hmac_sha256(key)`                   | HMAC-SHA256, where the `key` argument is parsed as a hex-encoded string and the output is hex-encoded                                                                                                                             |
| `encrypt(algorithm, public_key)`     | Asymmetrically encrypts the value to a public key. `algorithm` is `rsa_pkcs1v15` or `ecies_p256_x963_sha256_aes_gcm`, and `public_key` is a hex-encoded DER-formatted public key. RSA public keys must be in PKCS#8 format        |

Enclose string arguments to filter functions in `"` or `'` quotes.

For example:

* `{{ id.last_name | to_ascii | to_uppercase }}` converts `Doè` to `DOE`.
* `{{ id.dob | date_format("%Y-%m-%d", "%A in %B of %y") }}` converts `1988-12-30` to `Friday in December of 88`.
* `{{ custom.ach_account| replace("-", "") }}` converts `12-1212-1212` to `1212121212`.

## Ingress vaulting

Ingress vaulting rules pick fields out of the destination's response and vault them, so the proxy tokenizes incoming data as well as detokenizing outgoing data.

You can define all of your ingress rules in the dashboard or through the admin API and invoke the configuration by its `id` at `api.onefootprint.com/vault_proxy/{proxy_id}`.

A saved configuration does not say which user's vault to write to, so a request that uses its ingress rules must carry this header:

| Header    | Format    | Value                                    |
| --------- | --------- | ---------------------------------------- |
| `x-fp-id` | `<fp_id>` | The user whose vault receives the values |

For example, send the header `'x-fp-id: fp_id_tctecBEvGc98V7Vx4MhZU'` when the configuration's rule is `custom.credit_card_number=$.data.card.number`.

### Just-in-time ingress vaulting

Without a saved configuration, pass each rule as an `x-fp-proxy-ingress-rule` header in the form `<footprint_token_id>.custom.<property_name>=<path-to-value>`, where the path syntax depends on the response content type. When one or more `x-fp-proxy-ingress-rule` headers are present, you must also send a single `x-fp-proxy-ingress-content-type` header. The left side of a rule uses the same token format as the body, without the `{{` and `}}` delimiters.

### JSONPath

Ingress supports JSON responses only, with [JSONPath](https://github.com/json-path/JsonPath) to select the value. This rule vaults a card number from the response:

```bash
-H 'x-fp-proxy-ingress-rule: fp_id_tctecBEvGc98V7Vx4MhZU.custom.credit_card_number=$.data.card.number'
```

Given the response below, it extracts `4242424242424` and vaults it as `custom.credit_card_number` in the vault of `fp_id_tctecBEvGc98V7Vx4MhZU`:

```json
{
  "data": {
    "card": {
      "number": "4242424242424",
      "expiration": {
        "month": "04",
        "year": "25"
      }
    },
    "processor": "amex"
  }
}
```

| More examples                                                                     |
| --------------------------------------------------------------------------------- |
| `fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.number=$.data.card.number`              |
| `fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.exp_month=$.data.card.expiration_month` |
| `fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.exp_year=$.data.card.expiration_year`   |
| `fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.cvc=$.data.card.security_code`          |

To reformat a value before it is vaulted, append [filter functions](#basics-filter-functions) to the right of the JSONPath selector:

```
<fp_id>.<data_identifier> = <json_path> | <filter_function> | <filter_function> | ...
```

For example, given this response body:

```json
{
  "card": {
    "number": "4242-4242-4242-4242"
  }
}
```

this rule strips the dashes before vaulting:

```
fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.number = $.card.number | replace("-", "")
```

The result is `fp_id_tctecBEvGc98V7Vx4MhZU.card.primary.number` with the value `4242424242424242`.

## Reflection

The `reflect` endpoint runs the token substitution and returns the result to you instead of forwarding it to a destination: the "first hop" of the proxy on its own. Use it to decrypt data into a complex object or to test your Vault Proxy configurations.

For example, this request returns a sentence with two vault fields filled in:

```bash
curl https://api.onefootprint.com/vault_proxy/reflect \
      -u sk_test_0Te2YtSveZpWLMjQgNRkCv6siiC86iMIkZ: \
      -H 'x-fp-id: fp_id_JHSfbHz7VdxfoPXuaOlZqb' \
      --data \
      'The name on my credit card is {{ card.primary.name | to_ascii | to_uppercase }}. I was born on a {{ id.dob | date_format("%Y-%m-%d", "%A in %B of %y") }}.'

The name on my credit card is JANE DOE. I was born on a Friday in December of 88.

```

Send a `POST` to `/vault_proxy/reflect` with the body written in proxy token syntax, filter functions included. Add an `x-fp-id` header to use inferred-user tokens and omit the `fp_id` from each token.

## Advanced proxy settings

Vault Proxy also supports mTLS (client certificates), certificate pinning (custom server certificates), and vaulting selected parts of the response. The sections below configure them with just-in-time headers. We recommend saving them in the dashboard instead: it is simpler and more secure, because you do not manage authentication secrets for the destination service yourself.

### Client certificate authentication (mTLS)

To connect to the destination with mutual TLS (mTLS), supply a client certificate and key. Only a single certificate and key pair is accepted. Encode both as [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail), then [percent-encode](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) them.

### Service certificate pinning and root CAs

Pinning is optional. To enable it, supply one or more server certificates or root CAs to validate the destination's server certificate against. When you supply several, the connection succeeds if any one of them validates the incoming certificate chain. Encode each as [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail), then [percent-encode](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) it.

## Header and configuration reference

Every header the proxy accepts, for configuring and invoking it just-in-time. Values stored in a saved configuration do not need to be sent on each request.

| Header                            | Usage                                           | Description                                                                                                                 |
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `x-fp-proxy-target-url`           | Required for just-in-time                       | The HTTPS URL Footprint routes the request to                                                                               |
| `x-fp-proxy-method`               | Optional                                        | Selects the HTTP method (defaults to `POST`). Overrides the configuration's value if present                                |
| `x-fp-proxy-fwd-<HEADER>`         | Optional. Multiple allowed                      | Sends a header named `<HEADER>` with the corresponding value                                                                |
| `x-fp-path-and-query`             | Optional                                        | Adds a path and/or query parameters to the proxy target URL                                                                 |
| `x-fp-id`                         | Optional                                        | The user the tokens refer to. Required when tokens in the body omit the `fp_id_` prefix                                     |
| `x-fp-proxy-access-reason`        | Optional                                        | The decryption reason recorded in the access and security logs during detokenization. Defaults to no reason                 |
| `x-fp-proxy-client-cert`          | Optional. Percent-encoded PEM                   | The client certificate to use                                                                                               |
| `x-fp-proxy-client-key`           | Optional. Percent-encoded PEM                   | The client certificate private key to use                                                                                   |
| `x-fp-proxy-pin-cert`             | Optional. Percent-encoded PEM. Multiple allowed | A root CA certificate or self-signed certificate to validate the server's certificate against                               |
| `x-fp-proxy-ingress-content-type` | Optional                                        | The content type of the response to process ingress rules on. Only `json` is supported today; `regex` and `xml` are planned |
| `x-fp-proxy-ingress-rule`         | Optional. `<token>=<path>`. Multiple allowed    | An ingress rule: the namespaced token to vault and the path of its value in the response                                    |

## Playground

Footprint's `ditto` server echoes the headers and body of any request sent to `https://ditto.footprint.dev`, so you can test a proxy configuration against it.

### Basic usage

A direct request shows what `ditto` echoes:

```bash
$ curl -i -X POST https://ditto.footprint.dev -H 'Test-Header: FootprintRocks'  --data '{"hello": "world" }'

test-header: FootprintRocks
content-type: application/x-www-form-urlencoded

{"hello": "world" }
```

Set `https://ditto.footprint.dev` as the proxy target to check that decryption and token templating produce the body you expect.

### Testing with client certificates

To test mTLS with a client certificate, send the request to `https://ditto.footprint.dev:8443` (port 8443). The server certificate is self-signed, so either trust it or pin it in the configuration. With a client certificate, the echo includes its serial:

```bash
$ curl --cert client.crt --key client.key -i -k -X POST https://ditto.footprint.dev:8443 -H 'Test-Header: FootprintRocks' --data '{"hello": "world" }'

x-ditto-client-cert-serial: 12431179266346922388
test-header: FootprintRocks
content-type: application/x-www-form-urlencoded

{"hello": "world" }
```

When a client certificate is used, `ditto` returns its serial in the `x-ditto-client-cert-serial` header.