Security and Privacy
Authentication model, data stored on your site, data sent to Stripe, GDPR Privacy Tools integration, rate limiter, uninstall behavior, and the WP-CLI surface for operators.
Authentication model#
The plugin uses passwordless magic-link authentication:
- Customers prove access to an email inbox — not a WordPress password.
- Links contain a random one-time token generated with
wp_generate_password(20 characters). - The token itself is never persisted. The plugin stores a SHA-256 hash of the token in a WordPress transient (key prefix
lscp_stripe_login_token_) with a one-hour TTL, mapped to the customer’s email. A database snapshot therefore does not expose unredeemed login links. - On click, the plugin re-hashes the URL token, looks up and immediately deletes the transient, then provisions the Stripe Billing Portal session.
Form submissions require a valid WordPress nonce to mitigate CSRF, and the admin settings page enforces the manage_options capability before rendering or saving.
Rate limiter#
v1.0.6 introduces a per-identity rate limiter so the magic-link form can’t be abused as a mail relay or enumeration oracle:
| Identity | Limit | Window |
|---|---|---|
| Lowercase email address | 5 requests | 10 minutes |
| Submitting IP address | 5 requests | 10 minutes |
Counters are stored as transients keyed by SHA-256(identity), never the raw email or IP. When either bucket overflows, the form returns “Too many requests. Please wait a few minutes and try again.”
An administrator can clear a stuck counter with WP-CLI:
wp lscp limiter-reset [email protected]
Email enumeration#
The form response is constant for valid, invalid, and unknown emails — and the wording is mode-aware:
| Setting | Confirmation message |
|---|---|
| “Existing Stripe customers only” checked | “If your email address is associated with a Stripe customer, a login link is on its way. Please check your inbox.” |
| “Existing Stripe customers only” unchecked (default) | “A login link is on its way. Please check your inbox for the link to access your Stripe Customer Portal.” |
In the default mode the response is unconditional — nothing in it reveals whether a Stripe customer already exists for that email (because the plugin will create one on first redemption). In the gated mode the conditional wording is honest about the gating without confirming customer existence.
Data stored in WordPress#
| Data | Storage | Notes |
|---|---|---|
| Stripe Secret API key | wp_options (lscp_stripe_api_key) | Fixed-length 12-char mask in admin UI |
| Redirect URL | wp_options (lscp_stripe_redirect_url) | |
| Portal slug | wp_options (lscp_stripe_endpoint_slug) | Sanitized, max 64 chars |
| Existing-customer flag | wp_options (lscp_stripe_validate_existing_customers) | 0 or 1 |
| Login tokens (hashed) | Transients (lscp_stripe_login_token_{sha256(token)}) | Email value, 1h TTL, one-time use |
| Rate-limit counters | Transients keyed by sha256(identity) | 10-minute window per email and per IP |
The plugin does not store customer payment details locally. Billing data remains in Stripe.
Data sent to Stripe#
When a customer uses the login flow, your server calls Stripe's API to:
- Search for customers by email
- Create a customer (when allowed)
- Create a Billing Portal session
By using this plugin, you consent to transmitting customer email addresses and related API data to Stripe under their terms.
GDPR / Privacy Tools#
v1.0.6 registers personal-data exporter and eraser callbacks with WordPress’s Privacy Tools so site owners can satisfy data-subject access and erasure requests through Tools → Export Personal Data and Tools → Erase Personal Data.
The exporter surfaces what the plugin holds for a given email:
- Whether an outstanding magic-link transient exists for that email (without revealing the token itself).
- Whether an active rate-limit counter exists for that email.
The eraser deletes both. The plugin intentionally holds almost no PII at rest, so the privacy surface is minimal — but the WP Privacy Tools wiring is present for compliance workflows.
Third-party services#
Stripe#
Required for Customer Portal functionality. All portal UI and payment method storage is on Stripe infrastructure.
Freemius#
The free plugin includes the Freemius SDK for licensing infrastructure and optional usage insights. On first activation you may see an opt-in prompt. Freemius may collect basic site and plugin metadata if you opt in. See Freemius privacy documentation linked from the opt-in screen.
Requirements#
| Requirement | Version |
|---|---|
| WordPress | 5.0+ (tested up to 6.9) |
| PHP | 7.4+ (bumped from 7.0 in v1.0.6; PHP 7.0–7.3 are end-of-life) |
| Stripe PHP SDK (bundled) | 16.1.0 |
| Stripe API version (bundled SDK) | 2024-09-30.acacia |
Server requirements#
- Pretty permalinks recommended for the custom login endpoint
- Working
wp_mail()or SMTP for magic-link delivery - Outbound HTTPS to Stripe API endpoints
- WordPress Transients API (default in WordPress)
WP-CLI commands#
v1.0.6 ships an operator surface under wp lscp:
| Command | Purpose |
|---|---|
wp lscp purge-tokens | Delete every outstanding magic-link token. |
wp lscp limiter-reset <email> | Clear rate-limit counters for that email. |
wp lscp send <email> | Send a magic link without going through the form (useful for delivery tests). |
wp lscp config | Dump current plugin settings; the Stripe Secret key is masked. |
A daily WP-Cron sweep also removes expired magic-link tokens and rate-limit counters — WordPress’s built-in transient GC is lazy and can otherwise leave expired rows in wp_options indefinitely on low-traffic sites.
Extensibility#
v1.0.6 does not expose filters for:
- Email subject/body
- Return URL override
- Portal session parameters
The plugin is refactored into focused units (Settings, TokenStore, TokenGC, StripeGateway, Mailer, RateLimiter, PortalController, FormRenderer, RewriteEndpoint, Shortcode, Privacy, Cli, Uninstall), and the public class surface on LSCP\Plugin from 1.0.x is preserved — but no public filter or action hooks are documented yet beyond lscp_fs_loaded (fired when the Freemius SDK initializes).
Uninstall#
v1.0.6 ships a proper uninstall.php routine. When you delete the plugin from the Plugins screen, every option and transient LSCP wrote is removed automatically:
- All four
lscp_stripe_*options (Secret key, Redirect URL, slug, existing-customer flag). - All
lscp_stripe_login_token_*transients. - All
lscp_rl_*rate-limiter transients.
The cleanup is wired through Freemius’s after_uninstall hook, so the uninstall feedback survey still fires. The Stripe Secret Key no longer lingers in wp_options after deletion.
To force the same cleanup without deleting the plugin (e.g. on a staging environment), you can still drop the rows manually:
wp lscp purge-tokens
wp option delete lscp_stripe_api_key
wp option delete lscp_stripe_redirect_url
wp option delete lscp_stripe_endpoint_slug
wp option delete lscp_stripe_validate_existing_customers