How customers log in

Storefront authentication is deliberately simple: email + password, a cryptographically signed session cookie, 7-day session. No OAuth, no SAML, no "magic link" today. This page walks through every path a customer can take to get from /store/{slug} to a signed-in session.

There are three entry points on the login page:

  1. Sign in — existing customer or contact with a password.
  2. Create one — self-serve registration as a brand-new customer.
  3. Set your password — for contacts who were invited by a staff member or a customer admin.

The login page

URL: /store/{slug}/login.

Layout:

  • Company name at the top with the first-letter tile.
  • Email + password inputs.
  • Sign in button.
  • Below, two secondary links:
    • Don't have an account? Create one/store/{slug}/register
    • Invited as a contact? Set your password/store/{slug}/setup-password

What Distribu checks on sign-in

When the form submits, Distribu tries two lookups in sequence:

  1. Primary customer login — find a customer in this company whose email matches and whose stored password matches the one provided.
  2. Contact login — if there's no customer match, look for a contact with that email and matching password, then resolve back to its parent customer.

If either succeeds, Distribu writes a customer_session cookie and redirects to /store/{slug}/catalog. The cookie payload includes the customer ID, company ID + slug, the contact role (if it was a contact login), and the display name.

If the customer's status is BLOCKED, sign-in is rejected with:

Your account has been suspended. Please contact the store.

All other failures return the generic:

Invalid email or password.

(Deliberately vague — we don't confirm whether an email exists on the system.)

Session duration

Sessions last 7 days from the moment of sign-in. The cookie is:

  • HttpOnly — not accessible to JavaScript.
  • Secure in production.
  • SameSite=Lax.
  • Cryptographically signed so it can't be forged.

There's no "Remember me" checkbox — every login gets a 7-day cookie. After expiry, the customer is bounced back to /store/{slug}/login on their next request.

Logging out

Every authenticated storefront page has a Sign out button in the top-right header. Clicking it clears the customer_session cookie and redirects to /store/{slug}/login. No confirmation dialog.

Self-registration (new customer)

URL: /store/{slug}/register.

This is how a new buyer becomes a customer without you lifting a finger:

  1. They open the storefront URL.
  2. Click Create one on the login page.
  3. Fill in: name, email, password (min 8 chars), confirm password.
  4. Submit.

On success, Distribu creates a new customer account with:

  • status = ACTIVE
  • creditLimit = 0
  • No price overrides.
  • No shipping addresses (they'll add one before checkout).

Then it writes a session cookie and drops them at the catalog.

Errors they might see

  • Email already registered. Please sign in instead. — there's already a customer (or a pending contact) with this email on your store.
  • Passwords don't match. — the confirm field didn't match.
  • Password must be at least 8 characters. — shorter than 8.
  • This store isn't accepting new customer sign-ups right now. Please contact the store. — shown when registration is disabled for the company (not a toggle in the dashboard today; we can flip it on request).

Invited contact flow

URL: /store/{slug}/setup-password.

When you add a contact to a customer (see Customer contacts) or when a customer admin adds their colleague, Distribu creates a contact with no password. The contact is in a pending state — they exist, but they can't sign in yet.

To finish onboarding, they go to /store/{slug}/setup-password (the Set your password link on the login page) and:

  1. Enter the email that was invited.
  2. Enter a new password.
  3. Confirm it.

Distribu looks up the contact by email, securely stores the new password, and signs them in — dropping them at the catalog with the same session cookie as a normal login.

If no matching pending contact exists, they see:

No pending account found for this email. Ask your account admin to invite you.

If the contact already has a password set, the same error fires — once you've set a password, use /store/{slug}/login like everyone else.

Deep-linking the email field

The setup-password URL accepts an ?email= query param which prefills the email field. If you paste the link into an invite email — Distribu doesn't send invitations automatically, so you'd send your own — use:

https://your-domain.com/store/{slug}/setup-password?email=them@acme.com

This saves the contact one step.

Two tiers of storefront users

A logged-in session can belong to either a primary customer or a contact. They look the same in the UI — same cart, same checkout, same orders list — but they differ in two ways:

Primary customerContact
Record typeCustomer accountContact under a customer
RolesN/AADMIN, BUYER, VIEWER
Can place ordersYesADMIN + BUYER yes; VIEWER no
Can manage contactsN/A (dashboard only)N/A (dashboard only)
Session contactRolenullADMIN / BUYER / VIEWER

The header shows the role as a small gray pill next to the contact's name — so a contact always knows which hat they're wearing.

VIEWER contacts see the full catalog and cart UI but get this error when they try to check out:

Your account does not have permission to place orders.

Password management

Today:

  • Changing your password — not supported from the storefront UI. There's no "Account" page. If a customer needs to reset theirs, you (the distributor) can set a new password on their behalf via the REST API, or delete and reinvite the contact.
  • Forgot password — no self-serve reset flow. Same workaround. Email reset is on the roadmap.
  • Password storage — passwords are securely hashed with a per-customer salt; plaintext is never stored.

If you need either of those and they're blocking a customer, email support@distribu.app — we can manually reset a password for any customer or contact on your account.

Session boundaries

A customer signed into acme-distributors who opens beta-foods/catalog will be bounced to beta-foods/login — sessions are scoped to a single slug. If they need to shop from both stores they need separate accounts (different email is fine; even the same email works, since customers are scoped per-company).


Next: Placing orders from the storefront.