ADR 0051 — Fidelity auth flow: 3-legged OAuth 2.0 with PKCE; no credential hand-off to Raxx
Status: Proposed — contingent on WIX partnership (ADR 0050)
Date: 2026-05-05
Deciders: software-architect, operator
Context doc: fidelity-broker-integration.md §6
Related ADRs: 0002, 0009, 0014, 0050
Context
Raxx must authenticate on behalf of each customer against the Fidelity WIX API. Several auth flows are available for broker API integrations:
- 3-legged OAuth 2.0 with PKCE — customer authorizes Raxx at Fidelity's consent screen; Raxx receives tokens scoped to the customer's account.
- Customer credential passthrough — customer provides their Fidelity username + password to Raxx; Raxx authenticates on their behalf.
- SAML federation — enterprise SSO pattern; not applicable for a retail SaaS product.
- Static API key issued by Fidelity to Raxx — Raxx holds a single key that grants access to all enrolled customers' accounts.
This ADR records the chosen flow and the rationale.
Decision
3-legged OAuth 2.0 with PKCE (authorization code grant).
The customer authorizes Raxx at Fidelity's consent screen (browser redirect). Raxx receives an access token and (if issued) refresh token scoped to the customer's account and requested scopes. Raxx never sees the customer's Fidelity password.
This is the same pattern used for Alpaca live-handoff (ADR 0014), normalized under the forthcoming BrokerAdapter abstraction (ADR 0052).
Token storage at rest:
Access token and refresh token are stored encrypted using envelope encryption (KMS-backed wrapping key, same pattern as alpaca_live_connections). No plaintext token column is permitted in the schema. This is an explicit, narrowly-scoped exception to invariant I1 (no stored credentials), identical to the exception already accepted in ADR 0009 / ADR 0014.
The exception applies only to fidelity_live_connections rows for Pro+ users who have explicitly enrolled live-handoff. Every other user has no Fidelity credentials stored.
Raxx's platform credentials (WIX OAuth client_id + client_secret):
These live in Infisical at /raxx/v1/{env}/fidelity/. Never in code. Rotatable without redeploy. Rotated via Velvet (FidelityOAuthClientAdapter, pre-staged-mint pattern identical to PostmarkSenderTokenAdapter).
Consequences
Positive
- Customer never shares their Fidelity password with Raxx. This is a non-negotiable UX and security property.
- OAuth consent screen at Fidelity.com provides an authoritative authorization record visible to the customer in their Fidelity account settings.
- PKCE prevents authorization code interception attacks without requiring a client secret on the redirect-callback side.
- Pattern is identical to Alpaca live-handoff — no novel auth code path needed.
- Customer can revoke Raxx's access at Fidelity at any time, and Raxx detects revocation on the next API call (graceful disconnection path).
Negative
- Invariant I1 exception applies. The exception is narrowly scoped (same as ADR 0014 Alpaca) but it exists. CI-grep check must be extended to
fidelity_live_connectionsforbidden columns. - Refresh token storage extends the exposure window beyond a single session. Mitigation: envelope encryption + 90-day re-consent cap.
- Token refresh path introduces failure modes (refresh token expired, revoked) that require re-auth UX.
Alternatives considered
Customer credential passthrough (Fidelity username + password stored by Raxx)
Rejected. This is the most obvious violation of invariant I1 — "the system must not have the ability to replay a credential." Storing or transmitting Fidelity credentials would also violate Fidelity's ToS and expose Raxx to catastrophic regulatory and liability risk. This option is not revisitable under any circumstances.
Static API key (Raxx holds one key, all customers' accounts)
Rejected. This model puts all enrolled customers' accounts behind a single credential. A key compromise would expose every customer simultaneously. Also unlikely to be offered by WIX at the retail-customer granularity level required.
SAML federation
Not applicable. SAML is an enterprise SSO pattern for workforce identity. Retail customers authenticating from consumer devices do not have a SAML IdP Fidelity can federate against.
Short-lived access-only (no refresh token storage)
Considered. If WIX issues non-refreshable short-lived access tokens (e.g., 1-hour), Raxx could operate with no stored refresh token — customer re-authorizes each session. This is a cleaner posture vis-à-vis invariant I1.
Deferred. WIX token lifetime and refresh availability are unknown until the partnership agreement is in place. If WIX tokens are short-lived and non-refreshable, the storage model simplifies (no refresh token row). The fidelity_live_connections schema accommodates both cases: refresh_token_ciphertext is nullable.
Revisit when
- WIX partner agreement reveals token lifetime and refresh grant availability.
- BLR confirms whether the partner agreement restricts encrypted token storage on Raxx infrastructure (BLR open question #4).
- A future design considers a "no-storage" session-scoped flow for users who prefer not to grant persistent access.