Ga naar inhoud

Authorization in GBO Basis

The Globaal Ontwerp calls this part "configurable authorization". Each source has a PDP that decides on every request, field by field, with rules written as policy. The FSC Inway of the source enforces the decision. This page shows the parts, the order of the checks, and the admission of parties. The Authorization concept page has the detail and the links to the code.

Overview › GBO Basis › Authorization

Five questions, five places

Authorization answers five separate questions. Each question has its own place in the chain:

flowchart LR req(["Request"]) subgraph s1["① Organization"] direction TB a1("Is this a known<br/>organization?") b1["FSC connection<br/>mTLS + PKI certificate"] a1 --- b1 end subgraph s2["② Permission"] direction TB a2("Can it use<br/>this service?") b2["FSC Manager<br/>contract after admission"] a2 --- b2 end subgraph s5["⑤ Validity"] direction TB a5("Is the request complete<br/>and well formed?") b5["Request mapper<br/>field list, limits, pins"] a5 --- b5 end subgraph s3["③ Basis"] direction TB a3("Consent<br/>or PID?") b3["Policy<br/>regime from evidence"] a3 --- b3 end subgraph s4["④ Scope"] direction TB a4("Which fields, whose<br/>data, which years?") b4["Policy<br/>rules per field"] a4 --- b4 end src[("Source")] req --> s1 --> s2 --> s5 --> s3 --> s4 --> src %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class req ext class src source class a1,a2,a5,a3,a4 basis class b1,b2,b5,b3,b4 ext class a2,a5,a3,a4 zoom style s1 fill:#F4F5F7,stroke:#D1D5DB style s2 fill:#F4F5F7,stroke:#D1D5DB style s5 fill:#F0FDF4,stroke:#16A34A style s3 fill:#F0FDF4,stroke:#16A34A style s4 fill:#F0FDF4,stroke:#16A34A click a2 "#admission-of-parties" "Admission" click a3 "#regime-from-evidence" "Regime" click a4 "#the-rules" "The rules" click a5 "#from-query-to-decision" "Decision pipeline"

Figure 1: The five authorization questions and where each one is answered. Grey: FSC, once per connection. Green: the PDP of the source, for every request.

Questions ① and ② belong to FSC and hold for the whole connection. The PDP answers ③ to ⑤ again for every request (ADR-0004). No central authorization server exists.

The parts

The PEP, the PDP and two PIPs work together as follows:

flowchart LR out["Outway of<br/>the caller"] subgraph src["At the bronhouder"] direction LR pep("FSC Inway<br/>PEP") subgraph pdp["PDP: OpenFTV v0.1.0"] direction TB map("FTV GraphQL mapper<br/>query → field list") eng("Rego policy<br/>engine + rules") end pout("Outway<br/>of the PDP") gw("Source gateway") end adm[("PIP: admission register<br/>parties, mandates")] tr[("PIP: consent register<br/>keys, consent status")] adl[("ADL")] out -- "request + raw body" --> pep pep -- "AuthZEN" --> map map --> eng eng -- "status, over FSC" --> pout --> tr tr -. "keys (JWKS), cached 5 min" .-> eng adm -. "pulled every 5 s" .-> eng eng --> adl pep -- "only after allow" --> gw %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class out ext class pep,map,eng,pout,gw basis class adm,adl store class tr dvtp class eng,map,tr,adm zoom style src fill:#FFFFFF,stroke:#6F94B8,stroke-dasharray:4 3 style pdp fill:#F4F5F7,stroke:#16A34A click map "#from-query-to-decision" "Decision pipeline" click eng "#the-rules" "The rules" click tr "../../dvtp/consent-service/" "Consent facility" click adm "#admission-of-parties" "Admission"

Figure 2: PEP, PDP and PIPs at one source.

The parts have these roles:

Part Role
PEP The FSC Inway. Its AuthZEN plugin sends every request with the raw body to the PDP and forwards the request only on an allow. There is no separate PEP service (ADR-0002).
Request mapper Turns the GraphQL body into a list of fields with their arguments, following the FTV GraphQL profile. It does nothing else (ADR-0013).
Policy Rego code that selects the regime, checks the consent proof and decides each field. The policy is data for the PDP, so a bronhouder changes it without a new build.
PIP: consent register (toestemmingsregister) Gives the status of a consent for every request, over FSC. Gives the keys to check the proof. DvTP only.
PIP: admission register Lists the admitted parties and the mandates of integrators. OpenFTV pulls it every five seconds.

From query to decision

The PDP takes a fixed series of steps for each request. The first step that fails decides the outcome:

flowchart TB in(["AuthZEN question<br/>with raw GraphQL body"]) m{"Mapper: complete<br/>field list?"} r{"Regime: how many<br/>consent tokens?"} cv{"Consent proof:<br/>signature, claims, status OK?"} b["Bind rules to each field:<br/>same regime · same FSC service ·<br/>rule names the field"] f{"Every field granted<br/>by a rule?"} allow(["ALLOW<br/>query goes to the source"]) d1(["DENY<br/>COVERAGE_UNVERIFIABLE"]) d2(["DENY<br/>two tokens"]) d3(["DENY<br/>consent reason"]) d4(["DENY<br/>FIELD_NOT_PERMITTED"]) in --> m m -- "no" --> d1 m -- "yes" --> r r -- "two" --> d2 r -- "one: consent regime" --> cv r -- "none: PID regime" --> b cv -- "no" --> d3 cv -- "yes" --> b b --> f f -- "yes" --> allow f -- "no, or no rule at all" --> d4 %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class in ext class m,r,cv,f,b basis class allow basis class d1,d2,d3,d4 dvtp class cv zoom click cv "../../dvtp/consent-service/#the-consent-proof" "Consent proof"

Figure 3: The decision pipeline of the PDP. Red: deny.

Three properties of this pipeline matter for the design:

  • Closed world. A field that no rule names is denied (NO_APPLICABLE_RULE). There is no separate catalog of allowed fields: the rules are the catalog (ADR-0009).
  • All or nothing. The PDP allows the request only if it allows every field. It never removes fields from a query.
  • Two layers of reasons. The consumer gets one coarse code, so it cannot learn the schema by trying field names. The administrator of the source sees the reason per field (REQ-06).

Regime from evidence

The evidence in the request selects the regime. A declared regime or a contract property plays no part (ADR-0031):

flowchart LR q{"X-GBO-Consent-Token<br/>in the request?"} c["Consent regime<br/>DVT0001 · LVG0001"] p["PID regime<br/>EUD0001 · EUD0002 · ASI0001"] x(["Deny"]) q -- "one" --> c q -- "none" --> p q -- "two" --> x %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class q basis class c dvtp class p eudi class x dvtp

Figure 4: Regime selection.

The regime only selects which rules apply. Each rule still checks its own basis: DVT0001 denies without a verified consent, and the PID rules deny if a consent token is present.

Only consent is positive evidence. A request enters the PID regime because it carries no token (F-70). For that reason, the PID rules admit only named callers: the EUDI adapter and the ASIP. These lists must never contain a DvTP consumer. The fix in the design is a verified PID as positive evidence (REQ-45).

The rules

Demo v0.9.0 has five rules. Each rule names its regime, the FSC services it serves, and the fields it can grant:

Rule Regime Service Grants
DVT0001 consent bri Income declaration fields, per consented year
LVG0001 consent lvg The ownership check of the LVG pilot
EUD0001 PID bri Income statement (inkomensverklaring) to the wallet, years 2024 and 2025
EUD0002 PID brp Death certificate (akte van overlijden) to the wallet
ASI0001 PID brp The BRP fields behind the ASI attributes, for the ASIP only

A rule binds to a field only on its own services. A field name such as Query.ingeschrevenPersoon exists in several schemas. Without this binding, a rule for one source grants the same field at another source (REQ-97, F-90).

Under consent, two checks look inside the query. The citizen must be the placeholder consent:identity, never a real number. Every requested year must have a consented scope, and a query without a year filter is denied. The PDP checks that the citizen comes from the proof. It never learns who the citizen is (F-77).

Admission of parties

Before a party can send requests, it needs an FSC contract. The FSC Manager of the source asks the same PDP whether to sign it. The contract policy reads the admission register and stops at the first failed check:

flowchart LR p(["Contract proposal"]) c1{"Well-formed<br/>request?"} c2{"Grant type 2 or 3?"} c3{"Has a<br/>counterparty?"} c4{"Register<br/>available?"} c5{"Party in<br/>the register?"} c6{"Party<br/>active?"} c7{"Admitted for<br/>this source?"} c8{"Delegation<br/>registered?"} ok(["Manager signs"]) no(["Stays proposed<br/>with a reason code"]) p --> c1 --> c2 --> c3 --> c4 --> c5 --> c6 --> c7 --> c8 --> ok c1 & c2 & c3 & c4 & c5 & c6 & c7 & c8 -. "no" .-> no %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class p ext class c1,c2,c3,c4,c5,c6,c7,c8 basis class ok basis class no dvtp

Figure 5: The eight checks for a contract, in order (ADR-0015).

Admission is data in a register, not a rule in the policy (REQ-61). An operator admits, pauses or widens a party without a policy change. A refused contract stays proposed, so the party can try again after admission (REQ-63). The contract decides who can connect. The request rules decide what each request can get (REQ-62).

Open points

The main open points in authorization are these:

  • The PID regime needs positive evidence of its own (F-70, REQ-45).
  • The ADL record does not hold the consent status that the PDP used, so a decision cannot be replayed from the record (F-72).
  • The FSC Inway of demo v0.9.0 loses the reason code on the way back to the consumer (detail).
  • How to model fields, recursion and arguments for future sources (Q-14).

Traceability