Skip to content

feat: capture MaxMind tracking token at signup#77

Merged
mattdjenkinson merged 1 commit into
mainfrom
feat/maxmind-device-tracking
May 11, 2026
Merged

feat: capture MaxMind tracking token at signup#77
mattdjenkinson merged 1 commit into
mainfrom
feat/maxmind-device-tracking

Conversation

@mattdjenkinson
Copy link
Copy Markdown
Collaborator

@mattdjenkinson mattdjenkinson commented May 11, 2026

Merge order (4 of 5)

Part of a cross-repo rollout enabling MaxMind device fingerprinting in the signup fraud check.

  1. auth-provider-zitadel — datum-cloud/zitadel-provider#109
  2. fraud — datum-cloud/fraud#39
  3. datum.net — datum-cloud/datum.net#1289
  4. auth-ui (this PR) — browser-side token capture and session-metadata attach
  5. infra — datum-cloud/infra#2412

Safe to merge in isolation: the tracker is gated on `NEXT_PUBLIC_MAXMIND_ACCOUNT_ID` and the env var is only set by the infra PR, so until infra rolls out this is a pure no-op.


Summary

Attach the MaxMind tracking token to the Zitadel session created at signup (not to the user) — device fingerprinting is intrinsically per-session, and keeping the token off the long-lived User resource avoids leaking a transient signal where it doesn't belong.

Key features/changes:

  • New client component `MaxMindTracker` initialises `window.__mmapiws`, injects `device.js` once, and polls the `__mmapiwsid` cookie until the token is available — persisting it to `sessionStorage` so the value survives the `/register` → `/register/password` route transition.
  • Layout gates the tracker on `NEXT_PUBLIC_MAXMIND_ACCOUNT_ID`, mirroring the existing Fathom / Marker.io toggles. Dev and preview builds never contact MaxMind.
  • All three register forms (password, passkey-only, IDP-incomplete) read the token from sessionStorage and pass it through `registerUser` / `registerUserAndLinkToIDP`.
  • The session helpers (`createSessionAndUpdateCookie`, `createSessionForIdpAndUpdateCookie`) and their underlying `createSessionFromChecks` / `createSessionForUserIdAndIdpIntent` callers accept an optional `metadata` map that is encoded as `SetMetadataEntry` on the Zitadel `CreateSessionRequest`.
  • `registerUser` attaches `{ "maxmind/tracking-token": }` to the session it creates after `addHumanUser`. `addHumanUser` itself is unchanged.
  • Document `NEXT_PUBLIC_MAXMIND_ACCOUNT_ID` in `next-env-vars.d.ts`.

Token capture is best-effort: if `device.js` hasn't returned the cookie before submit, the user proceeds without it and the fraud check falls back to IP/email/UA signals only.

Test plan

  • Run `pnpm dev` with `NEXT_PUBLIC_MAXMIND_ACCOUNT_ID=1313245`; open `/register`; in DevTools confirm `device.js` request to `device.maxmind.com` and `__mmapiwsid` cookie after page load.
  • Submit the register form; verify the Zitadel `createSession` request body contains `metadata={ "maxmind/tracking-token": }` (server-side log or proxy capture).
  • Repeat for the password and IDP-incomplete flows.
  • Confirm absence of `NEXT_PUBLIC_MAXMIND_ACCOUNT_ID` disables the tracker entirely (no network requests, no sessionStorage entry).

Move the browser-side device fingerprint out of user metadata and onto
the Zitadel session created during signup. Device fingerprinting is
intrinsically session-scoped: a single user can have many sessions
across many devices, and keeping the token on the session avoids
polluting a long-lived User resource with a transient signal.

Key features/changes:
- New client component MaxMindTracker initialises window.__mmapiws,
  injects device.js once, and polls the __mmapiwsid cookie until the
  token is available, persisting it to sessionStorage so the value
  survives the /register -> /register/password route transition
- Layout gates the tracker on NEXT_PUBLIC_MAXMIND_ACCOUNT_ID, mirroring
  the existing Fathom and Marker.io toggles, so dev and preview
  deployments never contact MaxMind
- All three register forms (password, passkey-only, IDP-incomplete)
  read the token from sessionStorage and pass it through registerUser
  / registerUserAndLinkToIDP
- registerUser / registerUserAndLinkToIDP forward the token as Zitadel
  session metadata (key maxmind/tracking-token) via the new metadata
  parameter on createSessionAndUpdateCookie /
  createSessionForIdpAndUpdateCookie / createSessionFromChecks /
  createSessionForUserIdAndIdpIntent. addHumanUser is unchanged
- Document NEXT_PUBLIC_MAXMIND_ACCOUNT_ID in next-env-vars.d.ts

auth-provider-zitadel's session apiserver surfaces the metadata entry
on the milo Session annotation iam.miloapis.com/maxmind-tracking-token
which the fraud service reads when constructing the minFraud request.

Token capture is best-effort: if device.js hasn't returned the cookie
before submit, the user proceeds without it and the fraud check falls
back to IP/email/UA signals only.
@mattdjenkinson mattdjenkinson force-pushed the feat/maxmind-device-tracking branch from 0c8f9f7 to 3168e91 Compare May 11, 2026 13:29
@ecv ecv self-requested a review May 11, 2026 15:49
@mattdjenkinson mattdjenkinson merged commit dc3a5c6 into main May 11, 2026
6 checks passed
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