Skip to content

Privacy Engineering

Building Privacy-Aware Feature Gates in a White-Label iOS Engine

Stephane Lanfranchi
· 6 min read
iOS Privacy ATT Consent Compliance

Case study overview

Role
iOS platform engineer responsible for the age-declaration service and integration work across the wider shared consent architecture.
Context
A configurable engine where analytics, advertising, location, social features, and purchase flows varied by application and user state.
Challenge
Resolve age, regional consent, TCF purposes, and ATT in the correct order, then make independent modules follow the same conservative decisions.
Contribution
Implemented age-category resolution and fallback, launch ordering, minor-aware gates, TCF interpretation integration, and feature adoption across the engine.
Outcome
A centralized technical policy boundary that prevents tracking systems from starting while privacy state is unresolved and keeps feature behavior auditable.
Abstract privacy gate diagram showing age declaration, consent, and disabled tracking paths.

Privacy work is rarely isolated to one screen.

When an app needs to adapt behavior based on age, consent, tracking authorization, and regulatory requirements, the technical challenge is not only showing a prompt. The challenge is making many independent features follow the same privacy contract.

I contributed to that kind of infrastructure in the GoodBarber iOS engine, especially around age declaration, child protection, TCF consent interpretation, and the way tracking-related features should behave for minors.

This work mattered because it touched ads, analytics, location, comments, chat, user lists, and purchase-related flows.

The Context

GoodBarber is a white-label platform. The same iOS engine powers many apps, each with different modules enabled:

  • ads
  • Firebase analytics
  • Facebook integrations
  • AppsFlyer
  • login
  • chat
  • comments
  • geolocation
  • in-app purchases

That means privacy behavior cannot be hardcoded for one app. It has to be driven by capabilities, settings, platform APIs, and user state.

On iOS, this also intersects with Apple’s App Tracking Transparency framework, Google’s User Messaging Platform, TCF consent values, and newer age-related platform capabilities.

The result is a matrix of conditions:

  • Is the app allowed to ask for tracking?
  • Is the consent form enabled?
  • Has the user consented to personalized ads?
  • Is the user considered a minor?
  • Should external ads be disabled?
  • Should geolocation be blocked?
  • Should comments or chat be restricted?
  • Should analytics start at all?

The engine needed a coherent answer to those questions.

The Age Declaration Layer

One part of the work I owned was the age declaration service.

The service provides a central way for the app to ask for the user’s age range when the platform supports it, cache the resulting category when appropriate, and expose simple APIs to the rest of the engine:

  • is the user an adult?
  • is the user a minor?
  • can this user comment?

Those methods are intentionally simple because the rest of the app should not need to understand every detail of the age declaration API. A comments button, an ad manager, or a location service should not implement age rules independently.

The service also needed conservative fallback behavior. On supported systems, a successful shared range is reduced to the category the engine needs and persisted. If the request fails or the user does not share a range, the service reuses a previously cached category when one exists. Without one, it resolves the current run as minor and returns an error so the UI can explain that verification failed.

That default matters. In privacy and child-protection work, failure modes should lean toward protection.

Launch Order Matters

One subtle part of this kind of system is launch ordering.

If analytics, ads, or consent flows start before age declaration, the app may briefly do the wrong thing for a minor. So the app needs to determine age-related state before enabling systems that depend on it.

The launch flow was structured so age declaration happens before Firebase configuration and consent request behavior for users who are allowed to continue into those flows. A minor result stops the tracking branch immediately. An adult result can continue to consent initialization; a verification failure remains conservative rather than defaulting to an unrestricted launch.

That is a good example of how privacy is not only an API wrapper. It affects application lifecycle.

Launch sequence diagram showing white-label capability checks, age declaration and cached fallback, a minor short-circuit, UMP consent initialization, TCF purpose interpretation, conditional ATT authorization, and activation of eligible analytics or advertising systems.
Age is resolved before systems that could track or request advertising consent. UMP, TCF, and ATT answer different questions and run in that order.

Feature Gates Across the Engine

Once age state exists, many parts of the app need to respect it.

For minors, the app should avoid or restrict behavior such as:

  • tracking
  • external ads
  • consent prompts that no longer make sense
  • geolocation requests
  • comment actions depending on settings
  • chat access
  • user list access
  • certain purchase-related flows without parental gating

The important architectural point is consistency.

If only one feature checks age state, the system is incomplete. The same central service needs to be used across the engine so the behavior is predictable. That is what turns a privacy requirement from a one-off fix into infrastructure.

The broader consent system involved shared work, and I am careful about how I describe ownership there. I did not build every part of UMP or ATT handling from scratch.

My contribution included consent interpretation through TCF purpose reading and integration with the wider privacy flow.

TCF consent values are stored as purpose strings. The app needs to interpret those values to decide whether analytics, crash reporting, personalized ads, or storage-related behavior should be allowed.

This is not glamorous code, but it is important. A consent popup is only useful if the rest of the app correctly interprets the user’s choices.

The ATT flow adds another layer. Even if consent exists, iOS tracking authorization has its own rules and status values. The app has to combine platform authorization, privacy-center configuration, module availability, and consent state.

The resulting sequence is intentionally conditional. UMP first initializes Google’s consent information and presents a form only when required and enabled. When consent is obtained, the TCF reader checks the purposes needed for personalized advertising. ATT is requested only when that interpretation means cross-app tracking is actually justified and the tracking resource is available. If personalization is not consented, ads may still be eligible through UMP’s non-personalized path, but the engine does not ask for ATT unnecessarily.

For minors, the answer should be simpler: tracking-related behavior should be denied or unavailable instead of asking for consent that should not be used.

Keeping Policy Separate from Feature Code

Privacy requirements, platform policies, SDK behavior, and regional obligations evolve independently. The app is not the place to reinterpret the law feature by feature; it needs a technical policy boundary that can be updated as product and compliance decisions change.

I prefer small central services for this:

  • one service owns age category
  • one reader interprets stored TCF purposes
  • one manager coordinates consent status
  • feature modules ask those services instead of duplicating policy

This makes the system easier to audit. If a product or compliance decision introduces a new restriction for minors, there is a known place to expose that state. If a feature needs to know whether tracking is authorized, it asks the consent layer rather than inspecting raw platform values everywhere.

Policy-gate diagram showing age, UMP, TCF, ATT, app settings, and module availability being translated by central services into decisions for analytics, external advertising, location, comments, chat, user lists, and parental gates.
Feature modules consume a small set of decisions instead of interpreting raw age, consent, and platform values independently.

The Engineering Boundary

The durable lesson here is that privacy behavior is a system concern, not a prompt. The important work is launch order, conservative fallbacks, cross-module gates, and a small number of shared services that translate approved policy into app behavior.

My contribution was to implement and integrate key parts of that boundary around age and consent constraints, alongside the wider privacy work owned by the team.

Continue reading

Related engineering work