Consent Models & GPC
IConsentResolver
Section titled “IConsentResolver”The consent resolver determines if a user has consented to a cookie category:
public interface IConsentResolver{ Task<bool> HasConsentAsync(HttpContext httpContext, CookieCategory category);}StrictlyNecessary cookies bypass consent checks entirely — the cookie manager
never calls HasConsentAsync for essential cookies.
Consent models
Section titled “Consent models”The cookie system supports four consent models, configured per tenant via the regulation profile:
| Model | Behavior | Regulation | | ----- | -------- | ---------- | | OptIn | Cookies blocked until explicit consent | GDPR, LGPD | | OptOut | Cookies allowed by default, blocked on explicit opt-out | CCPA | | Hybrid | OptIn for sensitive categories, OptOut for non-sensitive | Some US states | | None | No specific consent requirement | Jurisdictions without cookie law |
The consent model is resolved via ICookieConsentModelProvider — an optional
interface provided by the bridge package Granit.Privacy.Regulations.Cookies.
Global Privacy Control (GPC)
Section titled “Global Privacy Control (GPC)”The GPC signal (Sec-GPC: 1
HTTP header) indicates the user prefers not to have their data sold or shared.
IGlobalPrivacyControlSignal detects the header. The GranitCookieManager
suppresses cookies based on the consent model:
| Consent model | GPC active | Effect |
| ------------- | ---------- | ------ |
| OptOut (CCPA) | Yes | Suppress SaleOrSharing + Marketing only |
| OptIn (GDPR) | Yes | Suppress ALL non-essential categories |
| Hybrid | Yes | Suppress SaleOrSharing + Marketing only |
| Any | No | No effect — fall through to IConsentResolver |
Default implementations
Section titled “Default implementations”| Implementation | Behavior |
| -------------- | -------- |
| NullConsentResolver | Denies consent for all non-essential categories (default) |
| NullCookieConsentModelProvider | Returns null — no GPC suppression (default) |
| GlobalPrivacyControlHeaderSignal | Reads Sec-GPC: 1 header (always registered) |
Publishing the GPC discovery document
Section titled “Publishing the GPC discovery document”Honoring the Sec-GPC header is the inbound half of GPC support. The
GPC spec also defines an
outbound declaration: a public JSON document at /.well-known/gpc.json
through which the operator publicly attests that the site honors GPC. Several
US state privacy laws — CPRA (Cal. Civ. Code §1798.135(b)(1)), Colorado CPA,
Connecticut CTDPA — recognize this as the canonical way to opt into honoring
the signal.
Granit ships an opt-in endpoint in Granit.Privacy.Endpoints that publishes
the document at the host root.
Wiring
Section titled “Wiring”app.MapGranitPrivacy(); // existing privacy endpointsapp.MapGranitPrivacyGpcDiscovery(); // /.well-known/gpc.json — no-op when disabled{ "Privacy": { "GpcDiscovery": { "Enabled": true, "LastUpdate": "2026-01-15" // see below — this is a legal anchor } }}When Enabled is false (the default), no route is mapped and the path
returns 404 — which is the spec-prescribed behavior for non-publishers.
LastUpdate is a legal anchor, not a build timestamp
Section titled “LastUpdate is a legal anchor, not a build timestamp”Per the GPC spec, lastUpdate is “the time at which the statement of support
was made, such that later changes to the meaning of the GPC standard should
not affect the interpretation of the resource for legal purposes.” It pins
the interpretation of your declaration to the version of the GPC standard in
force at that date.
Host scoping — split-host deployments
Section titled “Host scoping — split-host deployments”The .well-known URI scheme (RFC 8615) is host-rooted: the discovery
document covers the host that responds to the request, not your
organization. If your front-end is served from a host distinct from the BFF
(e.g. app.example.com for a SPA, api.example.com for the .NET host), then
this endpoint only declares GPC support for the BFF host. Browsers, crawlers,
and regulators looking up app.example.com/.well-known/gpc.json will see a
404 unless you also publish the document there.
Two options for the front-end host:
- Static publication — drop a matching
public/.well-known/gpc.jsonin your Vite / Next / static-site build. BothLastUpdatevalues must stay in lock-step with the BFF declaration. - Reverse-proxy routing — configure your edge (Cloudflare, Azure Front
Door, Nginx) so that requests to
<front-host>/.well-known/*are routed to the BFF host. The single .NET endpoint then covers both hosts.
What opting in actually commits you to
Section titled “What opting in actually commits you to”Publishing gpc: true is a public, opposable declaration that your operation
honors GPC across the entire processing surface of the host — not only the
cookie layer Granit enforces. Before flipping the switch, audit:
- Third-party SDKs (analytics, marketing pixels, A/B testing, session replay)
loaded by the front-end — many do not respect
Sec-GPCby default and need configuration or replacement. - Server-side processing pipelines that share data with vendors (CDP, affiliate networks, retargeting partners) — they must read your inbound GPC state and adjust accordingly.
- Privacy policy text — should explicitly reference your GPC commitment.
If any of these don’t currently honor the signal, fix them first. A declaration backed by non-conforming behavior is worse than no declaration.
See also
Section titled “See also”- Cookies overview — package structure and module setup
- Klaro CMP — Klaro integration backed by
IConsentResolver - Regulation Bridge — Privacy.Regulations bridge for GPC enforcement
- Opt-Out (CCPA) — anonymous opt-out for CCPA sale/share
- Regulations — multi-regulation engine that drives GPC behavior