M MemberIntel KB

spec

Auth & Identity Layer

Covers the three signup paths converging into a unified user model, per-license MP OAuth signing keys, customer-OAuth-only Stripe (no Connect), Argon2id passwords with server-side sessions, account merge prevention, and the V1.5 trial state machine.

Auth and identity layer

The framing: identity is harder than it looks because the SPEC promises three different signup paths.

Most products have one or two ways to become a user. MemberIntel has at least three:

Path A: existing MP customer clicks the in-MP-admin banner. They’re already authenticated to their MemberPress site. The banner sends them to memberintel.com and they should be signed in within seconds without filling anything out. This is the SPEC’s primary acquisition channel — the data flywheel depends on it being friction-free.

Path B: standalone signup at memberintel.com with email and password. Someone sees marketing, lands on memberintel.com, signs up directly. They might or might not have MemberPress installed. They might be a prospect (V1.5 first-time creator) or an existing MP customer who happens to come in through this path.

Path C: V1.5 wizard from MP onboarding. New MP customer in the middle of MP’s own onboarding flow opts into “Set up FOR ME.” They get redirected to memberintel.com, with a 14-day Pro trial and card-required.

Each path has a different identity story, and the system has to handle all three converging into the same user model — without creating duplicate accounts, without losing the MP license linkage, without security holes in the bridges between systems.

The core decision: who is the identity provider.

Two patterns are viable. They have different tradeoffs and the choice is genuinely consequential.

Pattern 1: MemberPress is the identity provider for MP-connected customers, MemberIntel is the IdP for everyone else. MP customers authenticate through their existing MP license. Standalone customers authenticate through MemberIntel’s own email/password. Two paths, joined at a unified user record on the MemberIntel side.

Pattern 2: MemberIntel is always the IdP, with MP license as a linkage rather than authentication. Every user has a MemberIntel account with email/password. Connecting an MP site adds a license linkage to that account, but the authentication itself is always MemberIntel’s. MP customers can come through the banner via a “magic link” pattern that creates the account and signs them in transparently.

Pattern 1 is what the SPEC implies (“OAuth-style flow with MP license; standard email + password fallback”). It’s the right answer for V1 because it maps cleanly to the user’s mental model — “I already have a MemberPress account, why am I creating another one.” But it has a real cost: you have two authentication systems instead of one, two session models, two recovery flows. Every auth-related feature gets implemented twice.

Pattern 2 is cleaner architecturally but worse for the in-MP-admin banner experience because it implies even MP customers fill out a signup form, even if it’s pre-populated. That friction kills your data flywheel.

The hybrid that actually wins: Pattern 1 conceptually, with Pattern 2’s unified data model under the hood. Every MemberIntel user has exactly one user record with an email, a password hash (possibly empty for MP-only users), and zero or more linked identity sources. MP customers have an MP license linked to their record but no password. Standalone users have a password but no MP license. V1.5 wizard users start with both — their MP license linked at signup, plus optional password set later.

This means the user table has a single canonical user_id that everything else (entitlements, brain, audit logs, payments) hangs off. The authentication providers — MP license, email/password, eventually BuddyBoss in V2 — are pluggable and additive. A user can add an email/password credential to an MP-license-only account at any time, and the system treats them as the same user.

The MP license OAuth flow, in mechanical terms.

The SPEC calls this “OAuth-style flow with MP license.” Worth being concrete about what this means because the MP-side API surface is one of the SPEC’s open questions (Q7) and the answer shapes Phase 1 directly.

The flow looks like this:

  1. User in MP admin clicks the MemberIntel banner.
  2. MP server (running on the customer’s WordPress install) generates a short-lived signed token containing the customer’s MP license info and a redirect target. The signing key is shared between MP and MemberIntel — established during MP plugin setup or on first banner activation.
  3. User is redirected to memberintel.com/auth/mp-license?token=...
  4. MemberIntel validates the signature, looks up the user by license, and either signs them in (existing user) or creates an account and signs them in (first time).
  5. A session cookie is set, the user lands on their MemberIntel dashboard.

The security depends entirely on the signing key management. If the key leaks, an attacker can mint tokens for any MP license. If the key rotates, every MP install has to pick up the new key. The pattern that works:

The key is per-license, not global. Each MP install has its own signing key, generated when the customer first activates the MP-MemberIntel integration. The key lives in the MP database (the customer’s, not yours) and in MemberIntel’s per-tenant secret store. Compromise of one license affects only that license. Rotation is per-customer, on demand.

There’s a fallback question: how does MP know what MemberIntel’s URL is, and what happens if the key gets out of sync. The clean answer is that the MP plugin reaches out to a known MemberIntel endpoint to register itself on first activation, exchanging keys via TLS, with the key stored on both sides afterward. Subsequent banner clicks use that established key to sign tokens. If the keys get out of sync (admin reset MP, restored from backup, etc.), the user gets a “this MP install isn’t connected — please re-link” flow that re-runs registration.

This is more or less the OAuth client credentials pattern with per-customer credentials, which is well-understood. The implementation effort is real but bounded; it’s the kind of thing Claude Code can scaffold cleanly given a spec.

The Open Q4 question: Stripe Connect vs customer OAuth.

This is auth-adjacent because it’s about how you connect to the customer’s Stripe account, which determines what data you can read and how the customer experience feels.

Stripe Connect is the pattern where Caseproof (or MemberIntel) is registered as a Stripe platform, and customer Stripes are connected as accounts under the platform. Stripe Connect is what powers things like Shopify Payments — the platform sees aggregate data across connected accounts. The customer experience is one click: “Connect with Stripe” and they’re done.

Customer OAuth is the simpler pattern: the customer authorizes MemberIntel to read their Stripe via standard Stripe OAuth, and MemberIntel stores a refresh token. No platform relationship, no aggregation rights, just per-customer read access.

The SPEC notes this depends on whether the customer is on Caseproof’s existing Stripe Connect platform (which serves MemberPress’s own billing) or on their own Stripe. The realistic answer is: a meaningful fraction of MP customers run their own Stripe, because they want their members’ payments going to their own bank account, not through Caseproof’s platform. So you need customer OAuth either way. The question is whether Stripe Connect adds enough value to also support it, and the answer is mostly no for V1 — the cross-customer aggregation that Stripe Connect enables is exactly the V1 non-goal you backed away from in the SPEC (“no cross-customer benchmarks built from pre-existing Caseproof Stripe Connect / MP telemetry data”).

The recommendation: customer OAuth only in V1. It’s simpler, it works for everyone, and it doesn’t tempt the team into using Connect data in ways the SPEC explicitly forbids. V2+ can revisit if the Connect aggregation becomes useful for some specific feature, with proper legal cover.

What customer OAuth means in practice:

The user clicks “Connect Stripe” in MemberIntel settings. They’re redirected to Stripe’s OAuth flow with read-only scopes. Stripe redirects back with an authorization code. MemberIntel exchanges the code for a refresh token, stores the refresh token in Secret Manager keyed by user_id, and uses it to fetch access tokens as needed.

The refresh token storage is the part that earns scrutiny. It’s a long-lived credential that gives read access to the customer’s payment data. It lives in Secret Manager with strict IAM (only the sync service account can read it, and reads are audit-logged). The refresh token is rotated whenever the user re-authorizes. If a user disconnects their Stripe in MemberIntel settings, the refresh token is deleted from Secret Manager and revoked at Stripe.

This is also the answer to the OAuth refresh token storage question I flagged earlier. Same pattern works for any OAuth-connected service — Stripe today, BuddyBoss tomorrow, others later.

Standalone email/password: not exotic but worth getting right.

The standalone path is the boring but high-stakes one. Most security incidents at small SaaS companies happen here.

The pattern that works in 2026 and won’t embarrass you in three years:

Argon2id for password hashing. Bcrypt is fine but Argon2id is what everything new should use. The migration from Argon2id to whatever-comes-next is much cleaner than the bcrypt-to-Argon2id migration would be.

Password requirements should follow NIST 800-63B: minimum length (12 characters), no composition rules (“must have a number”), check against known breach lists via a HaveIBeenPwned-style API. The composition rules are counterproductive — they force users into predictable patterns. Length and breach-checking is what actually matters.

Email verification at signup, with the ability to use the account in a limited way before verification. Unverified accounts can browse and connect Stripe but can’t access the full chat or trigger any operation that sends email. This is a balance between friction and abuse prevention — verified-before-anything is too strict and hurts conversion; verified-for-nothing lets bots take advantage.

MFA available, optional for V1, required for users with elevated access (e.g., agency-tier users in V3 managing multiple sites). TOTP-based via apps like Authy or 1Password, not SMS — SMS-based MFA has known weaknesses and is no longer considered acceptable for security-sensitive flows.

Password reset via email magic link. Standard pattern, no surprises. The reset link is single-use, expires in 30 minutes, and rotates the session secret on use so any active sessions on other devices are invalidated.

Session management via signed JWT or server-side session — pick one and don’t mix. JWT is simpler operationally (stateless, can be validated anywhere) but has a known weakness around revocation. Server-side sessions (token stored in Redis or Postgres, validated per request) are easy to revoke but add a database read per request. For V1, server-side sessions are the safer call. The latency cost is small (Redis is fast, sessions are tiny), and the ability to log a user out instantly across all devices matters more than people initially think — you’ll want it the first time a customer says “I think someone got into my account.”

The MFA wrinkle for MP-license users.

MP customers who authenticate via license don’t have a password to protect with MFA. Their security is the security of their MP install. If their WordPress admin gets compromised, attacker can click the banner and they’re in MemberIntel.

This isn’t a MemberIntel-specific problem — any service that delegates to a third-party identity inherits the third-party’s security posture — but it’s worth being honest about. The mitigation: MFA can be enabled on the MemberIntel side independently, where on next sign-in (regardless of source) the user is prompted for a TOTP. Sensitive operations (Stripe disconnect, account deletion, agent actions in V1.5+, plan changes) prompt for re-authentication via TOTP if MFA is enabled, regardless of how the original session started.

This adds a security layer the customer’s MP install can’t bypass, while keeping the friction-free banner-click experience for users who don’t enable MFA.

Account merging: the cleanup problem you’ll inherit.

The single hardest auth scenario is account merging. A user signs up at memberintel.com with email A. Later, they install MP and click the banner. The banner-click signs them into a new account because the system doesn’t know the MP license is the same person. They now have two accounts with overlapping data.

The defenses, in order of importance:

At MP-license-link time, check if the email associated with the MP license matches an existing MemberIntel account. If yes, prompt the user: “We see you already have a MemberIntel account at email X. Link your MP site to that account?” If they confirm, the MP license is added to the existing account.

At standalone signup, ask if they have MemberPress installed. If yes, walk them through linking it during signup rather than after. This catches Path B users who would otherwise create a duplicate.

Provide a manual merge tool in support. For the cases where a user did create duplicates, support can merge accounts — moving entitlements, brain content, audit logs from one to the other. This is a rare operation but it has to be possible. Build it as an internal tool, not a customer-facing one — too easy to misuse otherwise.

The single biggest mitigation is to make the MP-license email visible during the link step and not auto-create duplicates silently.

The trial state machine: where auth meets billing.

V1.5 introduces the 14-day Pro trial with card-required. The trial state isn’t auth exactly, but it lives at the same layer because every authenticated request needs to know “is this user mid-trial, post-trial, in good standing?” The entitlement service from the previous conversation is the right home for trial state.

The flow:

  1. User completes wizard, enters card. MemberIntel stores the card in Stripe (not in MemberIntel — Stripe Customer object holds the card, MemberIntel holds the Stripe customer ID). User is created with tier=pro_trial, trial_start=now, trial_end=now+14d.

  2. Day 12: a scheduled job emails reminder. Failure of this email is a P1 incident — users not warned about charge is a brand and refund problem.

  3. Day 14: a scheduled job attempts to charge the card via Stripe. On success: tier=pro, trial_end=null. On failure: dunning sequence kicks in, tier remains pro_trial but a flag (charge_failed) is set, user has 7-day grace period. After grace: tier=free.

  4. If the user cancels mid-trial: tier=free immediately, trial state cleared.

The state transitions are all idempotent (running the day-14 job twice doesn’t double-charge — Stripe deduplicates by idempotency key). The state transitions are all logged to the audit dataset. Cindy gets a daily report of trial transitions: started, converted, cancelled, failed.

The thing that goes wrong if you don’t design for it: a user cancels their trial on day 13, then re-runs the wizard a week later, gets a new trial. The system needs to track trial history per user and either prevent or allow this based on policy. The reasonable policy: one trial per user, ever. Re-running the wizard after a cancelled trial gives them the wizard experience but no trial — they’re upgraded to Pro immediately or stay Free. Document this in the trial schema as an available_trial boolean rather than just inferring from history.

The decisions to make to move forward.

  1. Pattern 1-with-unified-data-model auth (single user record, multiple identity sources). Recommendation: yes.

  2. Per-license signing keys for the MP banner OAuth flow, established at MP plugin activation. Recommendation: yes — global signing keys are not viable.

  3. Customer OAuth only for Stripe in V1, no Stripe Connect. Recommendation: yes — the SPEC’s V1 non-goals foreclose the value of Connect.

  4. Argon2id, NIST-aligned password rules, breach checking, server-side sessions, optional TOTP MFA. Recommendation: yes, all of it, in V1.

  5. Account merge prevention via email-matching at link time, plus an internal merge tool for cleanup. Recommendation: yes.

  6. Trial state machine in the entitlement service, with one-trial-per-user policy and idempotent transitions. Recommendation: yes.

  7. Sensitive-operation re-authentication prompt regardless of original auth source. Recommendation: yes — this is what makes MFA actually useful on this product.

The natural next threads from here: the data sync pipeline architecture (the MP and Stripe pipelines have reliability problems, back-pressure questions, and schema-drift handling that are genuinely interesting), secrets management at depth (key rotation, the OAuth refresh token storage we touched on, KMS key hierarchy), or the AI eval suite as architecture (the structure that makes evals into a real release gate vs a feel-good document — important for V1.5 agent gating).

For: S Seth Shoultes B Blair Williams