Skip to content

Client-server configuration

PDV can run in two topologies:

  • Same-site - the vault and the consumer (e.g. a Webform) live on one Drupal site. No OAuth, no HTTP; the in-process LocalVaultClient is used. Just install pdv (and pdv_webform) per Installation.
  • Cross-site - the vault is on one site and the consumer is a separate site that reads/writes the user's vault over HTTP. This page covers that setup end to end.

The two sites play fixed roles:

Site Modules Holds
Vault pdv, pdv_server_api, simple_oauth, consumers the encrypted data, the OAuth provider, the consent ceremony
Consumer pdv_client, pdv_client_api, key (+ pdv_webform) the vault connections (URL + OAuth identity) and the per-user handles

A consumer site must not have a vault of its own (do not enable pdv there).


1. Vault side

1.1 Enable the server API and OAuth provider

composer require drupal/pdv drupal/simple_oauth drupal/consumers
drush en pdv pdv_server_api simple_oauth

Generate the OAuth token-signing keys at Configuration -> People -> Simple OAuth (/admin/config/people/simple_oauth) and set the token lifetimes. Keep the private key outside the web root.

1.2 Create the API scope

The cross-site routes require the access pdv api permission. Expose it as an OAuth scope so a token can carry it:

  1. Configuration -> People -> Simple OAuth -> Scopes -> Add Scope.
  2. Name it pdv_api, granularity Permission, permission access pdv api.

1.3 Register the consumer

At Configuration -> Services -> Consumers (/admin/config/services/consumer) add one Consumer per remote site:

  • Label / Client ID - note the client id; the consumer site needs it.
  • Secret - generate a strong secret; the consumer stores it (see 2.2). The vault keeps only a hash.
  • Grant types - enable Client credentials.
  • Scopes - select pdv_api.
  • User - set a dedicated, role-less service user as the consumer's default user. The client-credentials token acts as this user; the access pdv api permission comes from the scope, so the service user needs no roles. (Never use an admin account here.)
  • Tenant - on a multi-tenant site, pick the realm this consumer acts in. The binding is immutable once saved, and the consumer can only read and write vaults in that tenant. The field is hidden on single-tenant sites (every consumer uses the default tenant).

1.4 Allow the consumer's return origin

The consent ceremony only redirects back to allow-listed origins. At the PDV server API settings, add each consumer site's origin (scheme + host) to the return-URL allow-list. A return URL outside the list is refused (guards against an open redirect that would exfiltrate the state and handle).

1.5 Owner permissions

Each user who owns a vault needs manage own pdv vault. They review and approve consumer requests from their vault page at /user/{user}/vault.


2. Consumer side

2.1 Enable the client

composer require drupal/pdv drupal/key
drush en pdv_client key

(pdv_client_api is pulled in automatically. Add pdv_webform if you want the form element.)

2.2 Store the client secret in a Key

The OAuth client secret is never kept in this module's configuration. Create a Key for it at Configuration -> System -> Keys (/admin/config/system/keys):

  • Key type: Authentication.
  • Key provider: for production use the environment or file provider (or a secrets manager), so the secret stays out of config exports. The configuration provider works for local development only.
  • Value: the secret issued for this consumer in step 1.3.

2.3 Add a vault connection

A vault connection bundles a vault URL with the OAuth identity this site authenticates as on that vault. A consumer site may have several (one per vault, or several identities on one vault); each Webform then names the connection it uses. At Configuration -> Services -> PDV Client -> Connections (/admin/config/services/pdv-client/connections) -> Add vault connection:

  • Label - a human-readable name, e.g. the vault it points to.
  • Vault base URL - scheme and host only, e.g. https://vault.example.
  • OAuth client id - the client id from step 1.3.
  • OAuth client secret - select the Key from step 2.2.

Then, on the Settings tab (/admin/config/services/pdv-client), pick the default connection: the one used whenever a caller does not name a specific connection. The client authenticates to each connection's vault with the client-credentials grant and caches the bearer token (per connection) until shortly before it expires.

The vault base URL and its OAuth token endpoint are operator-trusted outbound destinations: only an administrator (with administer site configuration) can set them, and a Webform can only select an existing connection, never supply a URL. Point connections at public vault origins; restricting outbound traffic to those hosts (egress filtering) is a network-layer concern outside the module's control.

2.4 Where the handle lives on the consumer

After a user links their vault (step 3), the vault returns an opaque handle for that (connection, user) pair. The consumer stores it in user.data (keyed vault_handle:<connection_id>), so a user linked on several connections keeps one handle per connection. The handle is safe to store at rest: it is the vault uid encrypted (AEAD) under a per-consumer key, so it reveals no vault uid and is useless without that connection's OAuth secret. It is the only user reference the consumer ever holds or sends - never a raw uid.

A Linked users report (Configuration -> Services -> PDV Client -> Linked users) lists which local accounts hold a handle on which connection, with an Unlink action that drops the stored handle (the user re-runs consent the next time they use that connection). Only the (user, connection) pairing is shown - the handle value is opaque.

2.5 Kind labels and language

Kind labels are owned and translated on the vault (see Translating kinds). The consumer fetches the vault's label catalogue and caches it per connection and language, so forms show kind names in the user's language without a per-request call. Two controls sit at Configuration -> Services -> PDV Client (/admin/config/services/pdv-client):

  • Item-kind label cache lifetime (seconds) - how long the catalogue is cached before re-fetching (default 86400, one day). Lower values pick up new kinds and translations from the vault sooner.
  • Refresh kind labels - clears the cached catalogue immediately, so a label added or translated on the vault shows on the next page.

The consent ceremony also follows the user's language: the consumer forwards its interface language to the vault, so the consent pages render in that language when the vault has it enabled (and uses URL-based language negotiation).


A bearer token proves which consumer is calling but grants no access to any user's data. Each user links their vault once, and grants access per kind:

  1. On the consumer site, the user follows a "Grant access from your vault" link (e.g. the prefill banner on a PDV-mapped Webform). The client redirects them to the vault's consent landing with the requested kinds, a scope (read or write), an anti-CSRF state, and a return URL.
  2. On the vault the user reviews the request and approves it (sharing specific items and/or granting standing trust). The browser returns to the consumer with the outcome and an opaque handle for this (consumer, user) pair, which the client stores against the local account.
  3. The consumer then calls the API with that handle.

Read and write are orthogonal. A read link asks for read access; a save-back (write) link asks for write. Granting one does not grant the other, so a form that both prefills and saves needs both - the user is prompted for each as it is first needed.


4. Webform consumer (optional)

With pdv_webform enabled on the consumer:

  • Each Webform names the vault connection it acts as (so one site can read from and write to different vaults, or use different identities, per form). Leaving it empty uses the site's default connection.
  • On an element's edit form, the Personal Data Vault section binds it to a record field (kind.field) or, on a checkbox, marks it a save-back consent for a kind.
  • On load, mapped elements prefill from the user's vault (needs read); on submit, a ticked save-back consent writes the values back (needs write). The user is sent through the consent ceremony for whichever is missing.

See the Consumer guide for the element details and the API reference for the underlying calls.