How our analytics warehouse auth works in Admaxxer — admin token reads, per-workspace isolation, and the dormant JWT path
Admaxxer’s analytics surface is powered by a first-party analytics warehouse with a parameterized SQL query layer. This page documents how the Admaxxer server authenticates against that warehouse, why every dashboard read goes through a server-side proxy rather than the browser, how every query is fenced to a single workspace by parameter, and what the dormant browser-token path is reserved for. If you’re wondering “is my data isolated from another Admaxxer customer’s?”, this is the canonical answer.
TL;DR
- Every session-authenticated analytics read runs server-side. The warehouse credentials are injected as an
Authorization: Bearerheader on an outbound HTTPS call made entirely from the Admaxxer server. The credentials never leave the server — the browser only ever talks to Admaxxer’s/api/v1/analytics/*routes, never directly to the analytics warehouse. - Per-workspace isolation is enforced at the query parameter layer. Every analytics query declares
workspace_idas a parameter and filters by it. The Admaxxer server resolves the activeworkspace_idfrom the session cookie, validates the user’s membership, and only then runs the query. A user with no membership in workspace X cannot trigger a query that reads workspace X. - A dormant browser-token path exists in code but is intentionally not active in production. It is preserved for the future embeddable-widget surface, where browser-issued read tokens scoped to a single report and a single workspace are the only safe option.
The two canonical data flows
Admaxxer’s analytics has two distinct caller shapes and they authenticate against the warehouse differently.
Flow A — session-authenticated server-side reads (every dashboard endpoint)
This is the path used by /api/v1/analytics/summary, /summary-series, /source-medium, /marketing-acquisition/utm-coverage, the attribution drill-down, and every other endpoint that returns dashboard JSON to the React app.
- Browser issues a request to e.g.
GET /api/v1/analytics/summary?since=…&until=…with the session cookie attached. - Admaxxer’s authentication middleware resolves the session, attaches the authenticated user, and passes the request to the route handler.
- The route handler calls a server-side analytics proxy. This proxy:
- Resolves the active
workspace_idfrom the session. - Confirms a pixel website exists for that workspace (otherwise short-circuits with
data_status: 'no_pixel_website'). - Loads the warehouse credentials from server configuration — if missing, returns 503
missing_configrather than calling the warehouse.
- Resolves the active
- The proxy issues the query to the warehouse with
workspace_idbound and the credentials in theAuthorizationheader. - The warehouse runs the query with
workspace_idbound to that value and returns aggregated rows in JSON. - The Admaxxer server normalizes the response and ships it back to the browser.
The browser never sees the warehouse host or the credentials. From the browser’s perspective Admaxxer is a regular JSON API.
Flow B — the dormant browser-token path (reserved for the future widget surface)
A browser-token mint path exists in code and was historically wired into two routes — the dashboard metrics API and the public REST pixel API. Both routes have since been collapsed to Flow A (the server-side proxy); the mint path is preserved for the future embeddable-widget surface, where browser-issued tokens are the only safe option. Its design:
- Signed with a server-only secret. Minting fails fast if the secret is unset.
- Short TTL (a couple of minutes, max five) — not cached, freshly minted on every request as a simple replay defence.
- workspace_id is server-derived, never accepted from user input. It is baked into the token’s fixed parameters, which the warehouse applies server-side and refuses to override from a query param — so a compromised client cannot switch workspaces at the HTTP layer.
- Report name is whitelisted against a registered set — only known reports can be requested.
Why it’s currently unused: the dashboard and public REST surfaces were both collapsed to the server-side proxy so that the credentials never leave the server, the response is the literal report payload ({ data, meta, rows, statistics, data_status }), and the same hardening (request timeout, single retry, two-tier cache) is applied uniformly.
Why we keep the browser-token path: the embeddable-widget surface (currently in alpha) needs browser-issued tokens. When that ships, it turns Flow B on for that surface only. Flow A stays as the canonical path for every server-side dashboard and API consumer.
Per-workspace data isolation — how it actually works
The analytics warehouse is a single shared store from Admaxxer’s perspective — every Admaxxer customer’s pixel events land in the same event store tagged with a workspace_id column. Multi-tenancy is enforced at the query layer, not the storage layer. Three guarantees combine to make this safe:
1. Every query declares workspace_id as a parameter
Every analytics query the server runs declares workspace_id as a required parameter and filters by it — conceptually:
SELECT
sum(revenue) AS revenue_total,
sum(spend) AS spend_total,
count() AS event_count
FROM events
WHERE workspace_id = :workspace_id
AND ts BETWEEN :since AND :until
The parameter binding is enforced by the query layer — if workspace_id is missing from the request, the query is rejected. There’s no “forgot the WHERE” failure mode possible — the query author had to declare the parameter to use it.
2. Admaxxer always supplies workspace_id from the session
The server-side analytics proxy resolves workspace_id from the user’s authenticated session and confirms the user is a member of that workspace. The proxy never accepts a workspace_id argument from request bodies or query params — it’s always derived. Even a maliciously-crafted request body can’t coerce it to point at a different workspace.
3. Browser tokens bake workspace_id into fixed parameters
For the dormant browser-token path (Flow B above), workspace_id is not a free query param — it’s a fixed parameter on the token scope. The warehouse applies fixed parameters server-side and refuses to override them from the URL. So even if the browser tries ?workspace_id=other-id, the warehouse ignores it and uses the token’s baked-in value. This is why the browser-token path is safe to expose despite browsers being untrusted clients.
The combined effect: a query for workspace A’s data cannot be made to return workspace B’s data, regardless of which auth path is in use.
Worked example — a single summary call
Suppose user alice@example.com is a member of a workspace and opens the dashboard. Here’s the call chain end-to-end:
- Browser:
GET /api/v1/analytics/summary?since=2026-04-01&until=2026-04-29with the session cookie attached. - The authentication middleware resolves the session and attaches the authenticated user, including their active workspace.
- Route handler calls the analytics proxy with the requested report and date range.
- The proxy reads the workspace ID from the session and loads the warehouse credentials from server configuration.
- The proxy runs the summary query with
workspace_idbound and the credentials in theAuthorizationheader. - The warehouse runs the query with
workspace_idbound and returns:{ "data": [ { "revenue_total": 12345.67, "spend_total": 4321.00, "event_count": 8124 } ], "meta": […] } - Admaxxer normalizes and returns to the browser.
If alice attempts to forge a request claiming a different workspace_id (e.g. by editing localStorage or modifying the request), the session middleware ignores it — workspace_id is read from the session record in our primary database, not the request payload. The proxy never reads workspace_id from anything mutable by the browser.
Server configuration
- Warehouse credentials — required. The single workspace-scoped credential Admaxxer uses for every server-side analytics read. Stored in server configuration, never in the repo. Rotation is a one-line change followed by a deploy.
- Warehouse host — the endpoint the proxy targets. Configurable per environment.
- Browser-token signing secret — required for the dormant browser-token path (Flow B above) to mint signed tokens. Even though the path is dormant, the mint code fails fast on startup if this is missing — rather than silently failing later.
- Destructive-operation gate — a flag that gates destructive maintenance operations against the warehouse. Default off — only enabled for explicit maintenance windows. Every such call also writes an audit row.
Why a shared admin token instead of per-request JWTs?
Three reasons, in priority order:
- The JWT secret rotation hasn’t happened yet. Our analytics warehouse workspace was provisioned with a pre-existing JWT signing secret that doesn’t match the analytics credentials configured in our infrastructure. Until that’s rotated to match, every JWT we mint is rejected. Falling back to the admin token kept the dashboards working through R31 — an evergreen choice when your alternative is “the dashboards are broken until ops rotates a secret.”
- The admin token never leaves the server. The risk profile is identical to any other server-side credential (Stripe secret, Anthropic API key, our primary database password). Admaxxer’s log scrubbers strip
Authorizationheaders; the token never appears in stack traces, error reports, or audit logs. - Per-workspace isolation is achieved by the query parameter, not the auth token. A JWT scoped to one workspace would also filter by workspace_id at the query layer. The auth choice is independent of the isolation guarantee — both paths route through
WHERE workspace_id = …, regardless of whether the token is admin or JWT.
Once the JWT secret is rotated, Admaxxer will move the browser-side widget surface onto Flow B and keep server-side dashboard reads on Flow A. Two-path equilibrium, each used where it’s the best fit.
FAQ
Q: Is my workspace’s data isolated from other Admaxxer customers?
Yes. Every query filters by workspace_id, and Admaxxer always supplies your workspace_id from the authenticated session. A query for your data cannot return another customer’s rows, and vice versa. An automated test suite pins the cross-workspace boundary with explicit cases.
Q: Does the Admaxxer server share the admin token with the browser?
No. The credentials live in server configuration only. Every browser-bound response is regular JSON; the warehouse host and the bearer token are not exposed to client code. The browser talks only to Admaxxer’s own /api/v1/* routes.
Q: Why does the source code still have a JWT-mint helper if every route now proxies?
The future widget surface needs it. Browser-issued tokens for embeddable dashboards must be JWTs — an admin token would leak workspace-wide admin access if exposed to a browser. The mint helper will be wired up to that surface only, once our analytics warehouse’s workspace JWT secret is rotated to match the analytics credentials configured in our infrastructure. Until then it sits dormant. Both the dashboard route and the public REST API used to call it; both were collapsed to the proxy pattern in GL#318 to fix dead-on-arrival 401s.
Q: What happens if the analytics credentials are missing or invalid?
Every dashboard endpoint returns data_status: 'missing_config' with HTTP 503 — the React app shows an inert shimmer state with a banner directing the workspace owner to contact support. We deliberately don’t fall through to a no-auth warehouse call (which would be a security smell). Missing config is an ops issue surfaced explicitly, not a silent degradation.
Q: How do I rotate the admin token?
Two steps. (1) in our analytics warehouse settings, generate a new admin token. (2) Update the analytics-credentials secret in our infrastructure and redeploy — takes ~3 minutes. The old token can be revoked from the same settings page once the deploy lands. The underlying query definitions are unaffected by token rotation.
Related
Bring your own Anthropic key (BYOK) · Analytics deep dive · Developer & API · Documentation home