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
LocalVaultClientis 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:
- Configuration -> People -> Simple OAuth -> Scopes -> Add Scope.
- Name it
pdv_api, granularity Permission, permissionaccess 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 apipermission 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).
3. The consent round-trip¶
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:
- 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-CSRFstate, and a return URL. - 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.
- 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.