← Microsoft articles

Microsoft Entra / Fair Verification

Null Isn’t False: Building Fair Credential Verifiers

Why missing evidence should be “unverifiable,” not “invalid,” and how collapsing those states leads to unfair outcomes.

Shubham Anandani··5 min read

One of the subtle but damaging patterns in identity systems is treating null as false. If a credential lacks a field, or a proof is absent, some code paths flip a boolean to false and move on. In verification, this collapses “we don’t know” into “disproven.” The difference matters: missing evidence isn’t proof of falsehood.

The failure mode

A verifier expects a “proofOfX” field. The credential arrives without it. The code sets proofOfX=false. A downstream gate denies access. Later, it turns out “proofOfX” wasn’t required for this flow, or the holder didn’t have a way to request it yet. The deny was based on an absence, not a disproven claim.

What I learned

Model states, not booleans

Replace booleans with intent-rich types. For a given proof, consider states like Present, Missing, Expired, Revoked, Unverifiable (failed binding), and Invalid (malformed or bad signature). This simple taxonomy avoids flattening all “not present” into “false.”

Exhaustive decision logic at boundaries

At the verifier boundary, use exhaustive switches over the state. Force downstream callers to handle each branch. If your language supports pattern matching, lean on it to eliminate default fall-through behaviors.

Treat absence as an opportunity to request or fall back

If a required field is missing, decide whether to:

Prompt the holder to supply it (if applicable).

Proceed with reduced assurance and log the state.

Deny the request explicitly as “unverifiable,” not “invalid.” The right action depends on your policy and context; the key is keeping “unverifiable” distinct.

Make “invalid” a narrow, technical category

Invalid should mean “structured wrong,” “bad signature,” or “contradiction.” It shouldn’t include “not provided,” “not bound,” or “not present yet.” Narrow “invalid” ensures you don’t over-penalize users for the system’s constraints.

Propagate state through APIs and UX

Expose the state in API responses: include status: "unverifiable" with a short note. Avoid returning false or 0 as a sentinel for “missing.” In UX, explain that more evidence is needed or that a different path is available.

Beware implicit type coercions

JSON, databases, and ORMs often coerce nulls to zero or false. Audit these boundaries. Ensure your serializers and persistence layers preserve state labels rather than defaulting values.

Test for fairness: counterfactuals

Put fairness into tests. For example: if “proofOfX” is missing, does the system deny or ask for more evidence? Are there alternative flows? Keep tests that assert the difference between “unverifiable” and “invalid.”

Explicit verification outcomes

from dataclasses import dataclass
from typing import Literal

Status = Literal["accepted", "unverifiable", "invalid"]

@dataclass(frozen=True)
class FieldCheck:
    name: str
    status: Status
    note: str | None = None

Trade-offs and failure modes

Trade-offs

More state complexity vs simplicity: Explicit states add a little code. The clarity and fairness gains are worth it.

UX friction vs silent denial: Asking for more evidence can add steps. Silent denials are worse. Balance friction with guidance.

Logging granularity vs privacy: Notes improve debugging; ensure they contain no sensitive data.

Failure modes

“Hidden coercion”: Tooling collapses null to false. Solution: audit boundaries and enforce state labels.

“Catch-all invalid”: Development shortcuts treat everything negative as invalid. Solution: write exhaustive switches and linters.

“Unverifiable loop”: Repeated prompts for evidence without alternative paths. Solution: define fallback policies clearly.

Try it yourself

Replace boolean checks for critical credential fields with stateful outcomes (“accepted,” “unverifiable,” “invalid”).

Expose the state in your verifier’s API. Track how often “unverifiable” occurs and address UX with prompts or fallbacks.

Add tests that assert denials only occur for “invalid,” not “unverifiable.”