Skip to content

Granit.Privacy

Granit.Privacy implements GDPR data subject rights (Art. 15–18) and cookie consent management. Data export orchestration via saga, right to erasure with cascading notifications, legal agreement tracking, and a strict cookie registry with CMP integration.

  • Granit.Privacy GDPR data export saga, legal agreements, data provider registry
  • DirectoryGranit.Cookies/ Strict cookie registry, consent resolver, managed cookie operations
    • Granit.Cookies.Klaro Klaro CMP integration (EU-sovereign consent manager)
PackageRoleDepends on
Granit.PrivacyGDPR data export/deletion orchestration, legal agreementsGranit.Core
Granit.CookiesCookie registry, consent enforcementGranit.Timing
Granit.Cookies.KlaroKlaro CMP consent resolverGranit.Cookies
graph TD
    PR[Granit.Privacy] --> C[Granit.Core]
    CK[Granit.Cookies] --> T[Granit.Timing]
    KL[Granit.Cookies.Klaro] --> CK
[DependsOn(typeof(GranitPrivacyModule))]
public class AppModule : GranitModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddGranitPrivacy(privacy =>
{
privacy.RegisterDataProvider("PatientModule");
privacy.RegisterDataProvider("BlobStorageModule");
privacy.RegisterDocument(
"privacy-policy", "2.1", "Privacy Policy");
privacy.RegisterDocument(
"terms-of-service", "1.0", "Terms of Service");
});
}
}

Modules register themselves as data providers to participate in GDPR data export and deletion workflows:

privacy.RegisterDataProvider("PatientModule");

When a data subject requests export or deletion, the saga queries all registered providers and waits for each to complete.

stateDiagram-v2
    [*] --> Requested: PersonalDataRequestedEvent
    Requested --> Collecting: Query all providers
    Collecting --> Prepared: PersonalDataPreparedEvent
    Prepared --> Completed: ExportCompletedEvent
    Collecting --> TimedOut: ExportTimedOutEvent
    Completed --> [*]
    TimedOut --> [*]

The export saga orchestrates data collection across all registered data providers. Each provider prepares its data independently, and the saga assembles the final export package.

{
"Privacy": {
"ExportTimeoutMinutes": 5,
"ExportMaxSizeMb": 100
}
}
sequenceDiagram
    participant User
    participant API
    participant Saga
    participant Identity
    participant BlobStorage
    participant Notifications

    User->>API: DELETE /privacy/my-data
    API->>Saga: PersonalDataDeletionRequestedEvent
    Saga->>Identity: DeleteByIdAsync (hard delete)
    Saga->>BlobStorage: Delete user blobs
    Saga->>Notifications: Delete delivery records
    Identity-->>Saga: IdentityUserDeletedEvent
    Saga->>User: Acknowledgment notification

Track consent to legal documents (privacy policy, terms of service):

privacy.RegisterDocument("privacy-policy", "2.1", "Privacy Policy");

Provide a store implementation to persist agreement records:

privacy.UseLegalAgreementStore<EfCoreLegalAgreementStore>();

ILegalAgreementChecker verifies if a user has signed the current version of a document — useful for middleware that blocks access until consent is renewed after a policy update.

Every cookie must be declared at startup. Writing an undeclared cookie throws UnregisteredCookieException (when ThrowOnUnregistered is enabled).

[DependsOn(typeof(GranitCookiesModule))]
public class AppModule : GranitModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddGranitCookies(cookies =>
{
cookies.RegisterCookie(new CookieDefinition(
Name: "session_id",
Category: CookieCategory.StrictlyNecessary,
RetentionDays: 1,
IsHttpOnly: true,
Purpose: "Session identification"));
cookies.RegisterCookie(new CookieDefinition(
Name: "analytics_consent",
Category: CookieCategory.Analytics,
RetentionDays: 365,
IsHttpOnly: false,
Purpose: "Analytics tracking preference"));
cookies.UseConsentResolver<KlaroConsentResolver>();
});
}
}
CategoryConsent requiredDescription
StrictlyNecessaryNoSession, CSRF, authentication
FunctionalityYesPreferences, language
AnalyticsYesUsage tracking
MarketingYesAdvertising, retargeting
OtherYesUncategorized

Always use IGranitCookieManager instead of IResponseCookies:

public class SessionService(IGranitCookieManager cookieManager)
{
public async Task SetSessionCookieAsync(
HttpContext httpContext, string sessionId)
{
// Checks registry, verifies consent, applies security defaults
await cookieManager.SetCookieAsync(
httpContext, "session_id", sessionId)
.ConfigureAwait(false);
}
public async Task RevokeAnalyticsAsync(HttpContext httpContext)
{
// Deletes all cookies in the Analytics category
await cookieManager.RevokeCategoryAsync(
httpContext, CookieCategory.Analytics)
.ConfigureAwait(false);
}
}

Security defaults applied automatically:

  • Secure = true (HTTPS only)
  • SameSite = Lax (CSRF protection)
  • Expires calculated via IClock.Now + RetentionDays
  • HttpOnly per cookie definition

The consent resolver determines if a user has consented to a cookie category:

public interface IConsentResolver
{
Task<bool> ResolveAsync(HttpContext httpContext, CookieCategory category);
}

StrictlyNecessary cookies always return true — no consent check needed.

Klaro is a self-hosted, EU-sovereign consent management platform.

[DependsOn(typeof(GranitCookiesKlaroModule))]
public class AppModule : GranitModule { }
{
"Klaro": {
"CookieName": "klaro"
}
}

KlaroConsentResolver reads the Klaro consent cookie and maps service consent to Granit cookie categories.

{
"Cookies": {
"ThrowOnUnregistered": true,
"DefaultRetentionDays": 365,
"ThirdPartyServices": [
{
"Name": "Google Analytics",
"Category": "Analytics",
"CookiePatterns": ["_ga*", "_gid"]
}
]
}
}
PropertyDefaultDescription
ThrowOnUnregisteredtrueThrow when writing an undeclared cookie
DefaultRetentionDays365Default cookie lifetime
ThirdPartyServices[]Third-party service declarations
CategoryKey typesPackage
ModuleGranitPrivacyModule, GranitCookiesModule, GranitCookiesKlaroModule
PrivacyIDataProviderRegistry, ILegalDocumentRegistry, ILegalAgreementChecker, GranitPrivacyBuilder, GranitPrivacyOptionsGranit.Privacy
Privacy eventsPersonalDataRequestedEvent, PersonalDataDeletionRequestedEvent, ExportCompletedEvent, ExportTimedOutEventGranit.Privacy
CookiesICookieRegistry, IGranitCookieManager, IConsentResolver, CookieDefinition, CookieCategory, GranitCookiesBuilderGranit.Cookies
KlaroKlaroConsentResolver, KlaroOptionsGranit.Cookies.Klaro
ExtensionsAddGranitPrivacy(), AddGranitCookies(), AddGranitCookiesKlaro()