Securing 2M+ accounts & $15B+ in assets, protected & secure·B5 Secure™ — per data-element authorization for .NET platforms·Now building on .NET 10

Five Authorization Decision Samples

← Developers
Developers

Five decisions, four of them denials.

A validated sample for each scripted case: allowed in scope, denied by scope, denied by threshold, revoked mid-flight, and a shadow evaluation that would have differed. The ratio is deliberate — that is what an authorization stream actually looks like.

5 samplesSchema-validatedTwo semantic invariants
Q4 2026 Roadmap Extension

The five cases

Each one exists because it is a case a reviewer will ask about.

01 · permit

Allowed, in scope

The grant covers the account and the action, the state is fresh, no limit is near. ALLOW_ reason code, because a permit needs a reason too.

02 · deny

Denied by scope

A valid, unexpired grant for a different account. Authenticated, session valid, endpoint permitted — and still wrong.

03 · deny

Denied by threshold

Carries both the limit that tripped and one that did not. Without limits[] this is indistinguishable from a scope denial.

04 · deny

Revoked mid-flight

The grant was withdrawn while the operation was moving. on_revoke_in_flight: fail is a property of the grant, so the answer was decided in advance.

05 · shadow

Would have differed

A simulated decision whose outcome differs from the enforced one. is_shadow: true is what stops this poisoning every metric built on the stream.

The instructive one

Sample 03. Note that it records a limit which did not trip.

samples/03-denied-by-threshold.json
{
  "category_uid": 3,
  "category_name": "Identity & Access Management",
  "class_name": "Authorization Decision",
  "metadata": {
    "version": "1.5.0",
    "product": {
      "name": "B5 Security Platform",
      "vendor_name": "B5 Secure",
      "version": "4.0.0.3"
    },
    "extension": {
      "name": "authority",
      "uid": "PENDING-OCSF-REGISTRATION",
      "version": "0.1.0"
    }
  },
  "tenant_uid": "t-8f21c0",
  "time": 1785225602789,
  "severity_id": 3,
  "activity_id": 9,
  "activity_name": "Transfer",
  "is_permitted": false,
  "reason_code": "DENY_LIMIT_DAILY_AMOUNT_EXCEEDED",
  "actor": {
    "user": {
      "uid": "advisor-338",
      "name": "j.okafor",
      "type": "User"
    }
  },
  "resource": {
    "uid": "AC-99120",
    "type": "custody_account"
  },
  "enforcement_point": {
    "type": "in_process_method",
    "runtime": "aspnet_core",
    "name": "Custody.Instructions.InstructionService.Submit"
  },
  "delegation": {
    "uid": "grant-4471",
    "grantor": "user-1042",
    "grantee": "advisor-338",
    "purpose": "portfolio_rebalance",
    "scope": "account:AC-99120;action:transfer",
    "start_time": 1785000000,
    "end_time": 1790000000,
    "on_revoke_in_flight": "fail",
    "is_revoked": false
  },
  "limits": [
    {
      "name": "daily_transfer_amount",
      "type": "amount",
      "window": "P1D",
      "threshold": 250000,
      "consumed": 250000,
      "remaining": 0,
      "is_exceeded": true
    },
    {
      "name": "daily_transfer_count",
      "type": "count",
      "window": "P1D",
      "threshold": 20,
      "consumed": 4,
      "remaining": 16,
      "is_exceeded": false
    }
  ],
  "policy": {
    "uid": "pol-cust-14",
    "name": "Custody Servicing",
    "version": "14"
  },
  "decision_evidence": {
    "uid": "ev-01J8ZB",
    "hash": "b7c1f0e2a9",
    "prev_hash": "4d0aa71cc3",
    "chain_uid": "chain-t-8f21c0",
    "sequence": 918235,
    "signature": "MEUCIQDf...",
    "signing_key_uid": "key-2026-07-a",
    "verifier_url": "https://b5secure.com/platform/decision-evidence/"
  },
  "is_shadow": false,
  "state_age": 90,
  "duration": 5
}

An analyst reading this knows three things a pass/fail field cannot tell them: which aggregate was consulted, how close the others were, and that the decision was enforced rather than simulated. That is the difference between a log line and evidence.

What the validator checks

Beyond required attributes, unknown attributes and enum ranges, the validator enforces two semantic invariants that a JSON schema cannot express:

Invariant 1

The reason must agree with the outcome

A denial carries a DENY_ code and a permit an ALLOW_ code. A record that says permitted with a denial reason is malformed even though every field is present and correctly typed.

Invariant 2

Shadow status is always explicit

Never absent, never inferred from a default. A stream where shadow status is optional is a stream you cannot safely build a detection on.

Four denials out of five is the honest ratio

A sample set of happy paths proves a product demos well. These exist so the failure modes are inspectable before you commit to anything.

Developer Relations

Talk to a human.

Get architecture guidance, Test Mode access, integration review, or help choosing the right B5 identity and authorization pattern.

Scroll to Top