Verification & enforcement
The primary flow — a partner embeds a signed consent object in a registry request, the Consent Manager validates it against the partner's policy, and returns the effective fields the registry may rele
This is the primary use case. A partner already holds consent (collected out-of-band or via the origination flow) and embeds a partner-signed consent object when it calls a registry API. The registry enforces; the Consent Manager (CM) decides.
Roles
Registry (PEP) — holds the data, extracts the embedded consent object, calls the CM, and releases only the fields the CM returns. It does not interpret consent.
Consent Manager (PDP) — verifies the object, evaluates policy, and returns a decision with the effective set of fields.
Request shape
The partner calls a registry endpoint and embeds the consent object (e.g. in a header, a consent body field, or per the interop protocol). The registry forwards it unchanged to the CM's /validate endpoint. /validate is on the partner API and requires no Keycloak token — trust rests on the partner-signed object (verified with keys from Partner Management) plus the jti replay guard; the registry↔CM hop is transport-secured (Istio mTLS).
POST /consent/v1/validate
{
"consent_jws": "eyJhbGciOiJFZERTQS...<compact JWS>...",
"partner_id": "PARTNER_SYSTEM_A",
"request_context": {
"requested_scopes": ["farmer_profile.basic", "farmer_profile.crops"],
"subject_id": { "type": "national_id", "value": "FARMER_1234" }
}
}The consent object is a compact JWS (RFC 7515); the CM recovers the claims from its payload and verifies the signature against the partner's Partner-Management key referenced by the JWS kid.
requested_scopes lets the CM intersect three sets: what the partner is asking for now, what the subject consented to, and what policy allows.
Validation pipeline (the PDP)
The CM evaluates these checks in order. The first failure short-circuits to deny with a precise reason_code; the denial is still logged.
1
The consent JWS decodes and its claims match the consent-object schema
malformed_object
2
aud (from the JWS claims) resolves to an active binding; the key for its partner_mgmt_id + JWS kid is fetched from PM (a 404 — unknown/disabled/no active key — is a fail-closed reject)
unknown_partner
3
The JWS alg is permitted (allowed set + policy allowed_signing_algs, matched to the PM key — algorithm-confusion guard), and the JWS signature verifies against that key
signature_invalid
4
aud == the partner, and data_controller == the module the partner was onboarded under (Partner.controller_id)
audience_mismatch
5
Subject present; subject_id_type ∈ allowed_subject_id_types
subject_not_allowed
6
purpose.code ∈ allowed_purposes
purpose_not_allowed
7
data_scopes ⊆ allowed_data_scopes
scope_exceeds_policy
8
now ∈ [valid_from, valid_until] and (valid_until − valid_from) ≤ max_validity_duration
expired / validity_exceeds_policy
9
consent_id not in revocation store
revoked
10
jti not seen before; issued_at within the freshness window
replay
Policy is a ceiling, consent is a request. Step 7 never widens scope. The effective scope is
requested_scopes ∩ data_scopes ∩ allowed_data_scopes. If that intersection is empty, the result isdenywithscope_exceeds_policy.
The verifying key comes from Partner Management, not a CM table. At step 2 the CM fetches the partner's public key (PEM) from PM by
partner_mgmt_id+kid, caching it per pod (soft/hard/negative TTL, unknown-kidrefresh). The signature check itself is always local to the CM. A404from PM fails closed. See Partner Management Integration.
Decision object (CM → registry)
On denial:
Enforcement at the registry (PEP)
The registry's contract is intentionally tiny:
Call
POST /consent/v1/validate.If
decision != "permit"→ return nothing (HTTP 403 withreason_code).Otherwise project the requested record down to
effective_data_scopesand return only those fields, echoingreceipt_idfor the partner's audit trail.
Because the CM returns the field list, the registry needs no consent logic — only a field-projection step. This is the deliberate division of labour decided in the design: CM returns the effective fields; the registry is a dumb enforcement point.
What the CM persists
On every evaluation (permit or deny) the CM writes an immutable DecisionLog entry. On a permit it additionally:
records the verified consent claims as a ConsentArtefact (
source = embedded),signs a ConsentReceipt with its own
.p12key (self-verifying via the CM's published JWKS), andstores both, returning
consent_id+receipt_id.
This gives non-repudiable proof — independently verifiable via the CM's published JWKS — for audit and dispute resolution.
Idempotency & performance
The hot path is a single round trip; partner keys (fetched from PM) are cached per pod, and the active policy version is cached (refreshed on an unknown
kid/ policy change).Re-validating the same object (same
jti) within its validity returns the sameconsent_id/receipt_idrather than minting duplicates.Revocation is checked live (see Consent lifecycle); enforcement points that cache decisions must respect the status endpoint.
Last updated
Was this helpful?