Skip to content

Add identity providers for ID5, LiveIntent, LiveRamp, ConnectID and UID2 - #1488

Open
dhruv8sh wants to merge 43 commits into
feat/s2s-eid-collection-corefrom
feat/s2s-eid-vendors
Open

dhruv8sh wants to merge 43 commits into
feat/s2s-eid-collection-corefrom
feat/s2s-eid-vendors

Conversation

@dhruv8sh

@dhruv8sh dhruv8sh commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #1487.

Summary

  • Adds server-side identity providers for ID5, LiveIntent, LiveRamp, Yahoo ConnectID, UID2, Prebid SharedID, Criteo and Unified ID (TDID). Each one plugs into the S2S EID collection framework from Collect server-side identity provider EIDs in a single page-load write #1487.
  • Adds a Lockr cookie capability, so a provider can take its ID from a Lockr-managed first-party cookie.
  • Adds the framework hooks the providers need. They could move into Collect server-side identity provider EIDs in a single page-load write #1487 if reviewers prefer:
    • providers can read their own first-party cookies, including every duplicate of a cookie name for opt-out checks
    • provider builds get the configured provider ID
    • lookups can read cookies and build an EID ext
    • providers can name sources they exclude where GDPR applies
    • a failed lookup logs its HTTP status

Per vendor

Provider Source Lockr capability
ID5 ID5 first-party cookie (including encoded cookies), else the /gs/v2 fetch API with an RFC 3339 ts and gpp_sid as an array; carries linkType and keeps ext; a universal_uid of 0 is an opt-out Yes
LiveIntent Lookup API that sends the LiveIntent first-party cookie; partner IDs are marked; needs a did- distributor ID; segments only when non-empty No
LiveRamp First-party envelope cookie (_lr_env, or the bare envelope Prebid stores in idl_env); with an endpoint, placement_id and custom_id_namespace, a visitor without a cookie is looked up server side by Custom ID Yes
ConnectID Yahoo's connectId first-party cookie; no server call No
UID2 First-party token cookie; with an endpoint, the token is refreshed server side before it expires Yes
SharedID Prebid _sharedid / _pubcid cookie as an atype 1 EID; honours _pubcid_optout; with generate = true (default off), generates a UUID stored only in the row No
Criteo cto_bidid cookie, stored verbatim as the atype 1 EID Prebid sends No
Unified ID TDID from the pbjs-unifiedid cookie, with rtiPartner ext and adserver.org inserter/matcher, mm 4 No

Consent and cookie handling

  • UID2 is excluded where GDPR applies, based on the visitor's detected jurisdiction. UID2 is not looked up there, UID2 IDs LiveIntent resolves are dropped, and UID2 IDs already stored in the row are removed.
  • A vendor cookie fills only the sources it covers; the provider's other due sources are still looked up, and the two results are merged.
  • The lookup rate limit is checked only once a request is built, so cookie-only providers use no budget.
  • Cookie readers try each value in turn, skipping empty values and IDs that have already expired; opt-out checks look at every duplicate of a cookie.
  • Cookie-supplied expiry is bounded, and vendor cookie opt-outs remove stored IDs.

Known limitations

  • ConnectID and SharedID opt-outs kept in localStorage are invisible to the server. A leftover connectId cookie is still read until it expires, and SharedID generation should be enabled only where the opt-out lives in the _pubcid_optout cookie.
  • LiveRamp server-side lookup needs Custom ID enabled on the placement and the publisher's namespace; it has not been tested against LiveRamp yet.
  • Logged-in and hashed-email inputs are not used by any provider.
  • Capturing Lockr responses is not supported.

The plans list the remaining unported reference behaviours.

Changes

Area Change
crates/trusted-server-identity-providers/src/{id5,liveintent,liveramp,connectid,uid2,sharedid,criteo,unifiedid}.rs Provider implementations and tests
crates/trusted-server-identity-providers/src/common.rs Shared helpers: cookie name lists, Lockr cookie capability, parsing
crates/trusted-server-core/src/ec/provider.rs, ec/coordinator/ Framework hooks for own-cookie reading, provider ID, EID ext, cookies in lookups, GDPR source exclusion, cookie/lookup merging, and HTTP status logging
crates/trusted-server-core/src/settings.rs, adapter-fastly/src/app.rs Passes the provider ID and registers providers
trusted-server.example.toml Example provider config, using example.com hosts except LiveIntent's fixed attribute sources
docs/guide/configuration.md, docs/guide/api-reference.md Provider configuration docs
docs/superpowers/plans/2026-11-02-identity-provider-*.md One plan per provider plus the Lockr capability

Closes

Part of #1246 and #1245. No issue is closed yet.

Spec

The spec is in #1487. This PR adds only plans, no new spec.

Test plan

  • cargo test-fastly && cargo test-fastly-reuse && cargo test-axum && cargo test-cloudflare && cargo test-spin
  • All 8 clippy aliases
  • cargo fmt --all -- --check
  • Parity tests, plus the template_build_digest test and its clippy run
  • scripts/lint-wasm-clock.sh
  • JS vitest, format and lint
  • Docs and markdown prettier
  • ./scripts/test-cli.sh, the node pin guard, docs-proposal scripts, and ts dev lint domains against the base branch
  • Local run against ID5's public test partner: the /gs/v2 fetch returned an ID that was stored in KV and attached to the auction
  • Local run reading a real LiveRamp envelope cookie into KV
  • LiveRamp server-side Custom ID lookup against LiveRamp (pending placement setup)
  • UID2 refresh against the UID2 service

Checklist

  • Changes follow AGENTS.md conventions
  • No unwrap() in production code
  • Uses log macros
  • New code has tests
  • No secrets or credentials committed

@dhruv8sh dhruv8sh self-assigned this Nov 2, 2026
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Add an optional IdentityProvider::read_request_cookies step that runs for providers with due sources before any lookup. Cookie IDs pass the same consent gates, validation and write plan as lookup results and replace the lookup. The provider endpoint becomes optional so cookie-only kinds need none; lookup kinds reject a missing endpoint.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Partner attributes such as bidswitch or magnite now carry the UID ext {provider: liveintent.com} that Prebid's LiveIntent module sets, while nonId stays bare. LiveIntent answers 400 Malformed did to any distributor ID that is not a did- ID, so the build rejects one.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Send _lc2_fpi as duid, resolve attributes from LiveIntent's fixed attribute table, keep reply segments as the nonId EID ext and mark the Trade Desk ID with matcher and rtiPartner, as the reference provider and Prebid do.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Prebid's identityLink ID module stores the envelope it fetches bare under its storage name, commonly idl_env, rather than the base64 JSON that ATS writes to _lr_env. Read idl_env after _lr_env by default and accept a base64-alphabet value that does not decode to JSON as a bare envelope.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Reads the publisher common ID from _sharedid, falling back to _pubcid, as a device-based pubcid.org EID, and honours the _pubcid_optout cookie.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Stores Criteo's bid ID verbatim as the device-based EID Prebid sends, with no server call.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Stores The Trade Desk ID with the rtiPartner ext, inserter, matcher and mm that Prebid sets.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Providers now report found IDs, an opt-out or nothing from their first-party cookies. An opt-out skips the provider's lookup as skipped_opted_out and removes its stored sources in the write plan, without a browser fallback. ID5 (universal_uid 0), UID2 (Prebid latestToken optout) and SharedID (_pubcid_optout) report opt-outs.

Cookies are read on every consented navigation, so an opt-out or changed ID is noticed while sources hold live UIDs; a cookie repeating the held UIDs is not rewritten, and lookups still respect due status.

UID2 parses Prebid's stored-tokens JSON and stores only latestToken.advertising_token, never JSON verbatim. Provider-stated absolute expiry is clamped to 30 days after the request in validation. Adds LiveRamp envelope length tests around the 1024-byte UID limit.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
@jwrosewell

Copy link
Copy Markdown
Collaborator

@dhruv8sh, the same point as on #1487. Directionally, eight identity vendors as files in one shared crate is not compatible with V2 (the 55 pull requests tracked in #777, merged in order), where each vendor is a module in a folder of its own at identity/<vendor> with its own named maintainer, so each provider owns and maintains its module.

The V2 spec is identifier-modules, with the folder layout in integrations-crate, both in #1342. The associated pull request is #1382 (a module hands core its identifiers, starting with the Prebid EID cookie).

Could you evaluate the V2 spec and #1382 before progressing this pull request?

…s server side

UID2 refreshes its stored advertising token with the stored refresh token and decrypts the AES-256-GCM reply; an optout reply removes the token. SharedID generates a random UUID v4 into KV only, kept for 30 days, when no cookie or stored ID exists. LiveRamp, when given an endpoint, placement ID and Custom ID namespace, looks up a RampID envelope by Custom ID built from the stable EC ID; a 204 reply is an opt-out.

Port the after-response collection, refresh-state, opt-out and rate_limit_per_hour framework changes, and update the vendor plans and operator docs.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
ts config validate, push and diff now build every enabled identity provider of a registered kind in both collection phases, so a provider-specific setting error (such as a liveramp endpoint without custom_id_namespace) fails the command before deploy. At runtime the same error disables every provider for each request.

Partner tokens are replaced with placeholders for the check, as core deploy validation already does.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
…'s jurisdiction is GDPR

Providers now name the sources they exclude where GDPR applies. For a visitor whose detected jurisdiction is GDPR, those sources are neither looked up nor stored, and the UIDs the row holds for them are removed. A TCF string alone no longer counts as GDPR applying.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>

# Conflicts:
#	crates/trusted-server-adapter-fastly/src/main.rs
#	crates/trusted-server-core/src/ec/coordinator/mod.rs
#	crates/trusted-server-core/src/ec/write_plan.rs
#	docs/guide/configuration.md
#	trusted-server.example.toml
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Also test merging cookie IDs with a failed, opted-out or found lookup.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants