---
title: "Login Flow"
description: "Step-by-step magic-link authentication flow from email entry to Stripe Customer Portal."
url: "https://docs.customerportalplugin.com/usage/login-flow/"
---
## Flow overview

1.  **Customer** enters email on the login form.
2.  **WordPress** verifies the nonce and applies the rate limiter (5/10min per email + per IP).
3.  **WordPress** optionally looks up the customer in Stripe by email.
4.  **WordPress** generates a 20-character token and stores its SHA-256 hash in a transient with a one-hour TTL.
5.  **WordPress** sends a magic link via `wp_mail()` using the configured Customer Portal slug.
6.  **Customer** opens the link in their email.
7.  **WordPress** re-hashes the URL token, looks up + deletes the transient (one-time use).
8.  **Stripe** finds or creates the customer.
9.  **Stripe** opens a Billing Portal session and redirects.
10.  **Customer** manages billing on Stripe's hosted portal.
11.  **Stripe** returns the customer to your configured Return URL.

## Step 1 — Customer submits email

The customer enters their email on:

*   The [dedicated endpoint](../customer-portal-endpoint/), or
*   A page with the [shortcode](../shortcode/).

WordPress verifies the form **nonce** (`lscp_stripe_portal_login_action`).

## Step 2 — Rate-limit + optional existing-customer check

Before any email is sent, the request is checked against the **rate limiter** (5 requests / 10 minutes, per email and per IP). If either bucket overflows the customer sees *“Too many requests. Please wait a few minutes and try again.”* See [Rate limiter](../../security-and-privacy/security-and-privacy/#rate-limiter).

If **Only allow existing Stripe customers to login** is enabled in settings:

*   The plugin queries Stripe for a customer with that email.
*   If none exists, **no email is sent**.
*   The customer sees the same enumeration-safe message either way.

If the setting is unchecked (default), the plugin proceeds without the Stripe lookup — any valid email gets a link, and a new Stripe Customer is auto-created when the link is redeemed.

## Step 3 — Magic link email

When allowed to proceed:

1.  A random **token** is generated (`wp_generate_password`, 20 characters).
2.  The plugin stores the **SHA-256 hash** of the token in a WordPress transient keyed by `lscp_stripe_login_token_{sha256(token)}`, mapped to the customer’s email, with **3600 seconds (1 hour)** TTL. The raw token is never written to disk — a database snapshot does not expose unredeemed login links.
3.  An HTML email is sent via **`wp_mail()`** with subject **Login to Stripe Customer Portal**.

![Example magic-link email sent to the customer](../../assets/images/email-login-link.png)

The link format (using your configured Customer Portal Slug, not a hardcoded path):

```
https://yoursite.com/{your-slug}/?token=XXXXXXXX
```

The customer sees a **mode-aware** confirmation message on the form:

*   Default mode: *“A login link is on its way. Please check your inbox for the link to access your Stripe Customer Portal.”*
*   Existing-only mode: *“If your email address is associated with a Stripe customer, a login link is on its way. Please check your inbox.”*

## Step 4 — Customer clicks the link

On `GET` with a valid `token` query parameter:

1.  WordPress computes `SHA-256(token)` and looks up the matching transient to load the email.
2.  The transient is **deleted** immediately (one-time use, even if the rest of the flow fails).
3.  If the token hash has no matching transient (expired, already used, or never issued), the customer sees: **Invalid or expired token.**

## Step 5 — Stripe Customer Portal session

The plugin:

1.  Sets the Stripe API key from settings.
2.  Finds a Stripe Customer by email, or **creates** one if none exists (unless blocked by existing-customer-only mode earlier in the flow).
3.  Creates a `BillingPortal\Session` with the configured **return URL**.
4.  Redirects the browser to Stripe's hosted portal URL.

![Stripe-hosted Customer Portal after successful login](../../assets/images/stripe-customer-portal.png)

## Step 6 — Return to your site

When the customer exits the portal, Stripe redirects to your **Redirect URL** (see [Settings Reference](../../configuration/settings-reference/)). If unset, the default is your customer portal login page.

## Security notes

*   **No WordPress accounts** are created for customers.
*   **Enumeration-safe messaging** — The post-submit message is mode-aware and does not reveal whether an email exists in Stripe.
*   **Hashed tokens at rest** — Stored as `SHA-256(token)`; a database snapshot can’t replay outstanding links.
*   **One-time tokens** — Each link works once and expires after one hour.
*   **Rate limiter** — 5 requests / 10 minutes per email and per IP, on every submission path (endpoint and shortcode).
*   **Nonces** protect form POST requests from CSRF.

See [Security and Privacy](../../security-and-privacy/security-and-privacy/) for the full model.

## Related docs

[Troubleshooting›](../../troubleshooting/troubleshooting/)
