Skip to content

User-scope cached billing token to prevent checkout under stale auth subject #592

Description

@AnthonyRonning

Summary

Maple caches the billing service third-party JWT in sessionStorage as maple_billing_token. That token is Maple/billing-server state, not OpenSecret SDK state, so the OpenSecret SDK should not know about it or clear it. Maple needs to clear or scope this token whenever the current OpenSecret user identity changes.

Right now the token is stored under a single unscoped key and reused until the billing API rejects it. If the OpenSecret auth identity changes while a still-valid billing token remains in the tab, billing calls can be authorized as the previous user while the UI reads email/profile state from the current user.

Suspected ordering

A plausible ordering that can produce incorrect checkout ownership:

  1. A browser tab has a valid cached maple_billing_token for User A.
  2. The OpenSecret auth tokens in localStorage change to User B through login/OAuth/deep-link/native auth or another auth transition.
  3. The cached billing token remains in sessionStorage because it is not tied to the OpenSecret user id.
  4. The pricing page builds checkout request data from current UI auth state for User B.
  5. BillingService reuses the stale billing JWT for User A.
  6. Billing server keys checkout/customer creation by the JWT subject, so checkout is created under User A while request body fields came from User B.

This is especially risky around selected-plan auto-checkout flows because checkout can be triggered immediately after auth/navigation.

Code paths to audit

  • frontend/src/billing/billingService.ts stores maple_billing_token as one global sessionStorage key and reuses it until rejected.
  • frontend/src/components/DeepLinkHandler.tsx writes new OpenSecret auth tokens and reloads without clearing Maple billing state.
  • Some signout paths call os.signOut() directly. Since the OpenSecret SDK should not know about Maple billing state, those Maple UI paths should clear billing state themselves before/after signout.
  • /login and /signup already-logged-in redirect effects can navigate to /pricing?selected_plan=... without clearing or validating the cached billing token for the current user.
  • Billing React Query keys such as ["billingStatus"] are not scoped by current user id.

Expected behavior

Maple should guarantee that billing requests use a billing JWT for the current OpenSecret user, or generate a fresh one before making the request.

Suggested fix

  • Make billing token ownership explicit in Maple, not the OpenSecret SDK.
  • Clear maple_billing_token whenever os.auth.user?.user.id changes or auth becomes logged out.
  • Consider storing the billing token under a user-scoped key, or decode/check the token sub before reuse.
  • Clear billing token in Maple-owned auth transition paths, including deep-link/native auth and signout UI paths.
  • Scope billing query keys by user id, for example ["billingStatus", userId].
  • Prevent selected-plan auto-checkout from running until billing token state has been validated/refreshed for the current user.

No customer identifiers or production-specific data are included here because this repo is public.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions