Auth — Storefront Registration
Customer sign-up for the storefront: creates an account and returns an authenticated session in one step. Phone number (E.164) is required.
Storefront customer registration via POST /store/auth/register. The endpoint creates the customer account and immediately returns an authenticated session — the caller is logged in without a separate sign-in step.
Source:
api-modules/auth/src/controllers/store-auth.controller.ts.Admin invite, vendor-org invite, and the generic better-auth endpoint
/auth/sign-up/emaildo not require a phone number — only this storefront path does.
Conventions
Authentication
| Endpoint | Auth |
|---|---|
POST /store/auth/register | none (public — creates session) |
Response envelope
Successful registration returns a 201 wrapped by ResponseInterceptor:
{
"data": <payload>,
"message": "Success",
"statusCode": 201
}Errors follow the standard error envelope:
{
"message": "<reason>",
"statusCode": 400,
"errorCode": "VALIDATION_ERROR"
}Session cookies
On success the response sets better-auth session cookies:
| Header | Value |
|---|---|
Set-Cookie | better-auth.session_token=<token>; Path=/; HttpOnly; SameSite=Lax |
set-auth-token | Bearer token string (for clients that prefer header-based auth) |
The customer is fully authenticated after a successful 201 — no separate login call needed.
Error envelope
statusCode | errorCode | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Malformed body — bad email, password < 8 chars, non-E.164 phone |
| 422 | UNPROCESSABLE_ENTITY | Email address already registered |
| 409 | CONFLICT | Phone number already in use |
Domain types
RegisteredUserResponse
type RegisteredUserResponse = {
id: string;
email: string;
name: string;
phoneNumber: string; // E.164, e.g. "+9779812345678"
phoneNumberVerified: false; // always false at registration
};
phoneNumberis unique per store installation. Attempting to register with an already-used phone returns409.
Endpoints
POST /store/auth/register — Customer sign-up
Creates a new customer account and returns an authenticated session. Phone number is required and must be in E.164 format.
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name |
email | string | yes | Valid email address |
password | string | yes | 8–128 characters |
phoneNumber | string | yes | E.164 format — e.g. +9779812345678 |
Request example
{
"name": "Priya Sharma",
"email": "priya@example.com",
"password": "securePass1",
"phoneNumber": "+9779812345678"
}Response 201
{
"data": {
"user": {
"id": "01J9xxxx...",
"email": "priya@example.com",
"name": "Priya Sharma",
"phoneNumber": "+9779812345678",
"phoneNumberVerified": false
}
},
"message": "Success",
"statusCode": 201
}Along with the JSON body, the response sets Set-Cookie: better-auth.session_token=... (and set-auth-token for bearer clients) — the customer is logged in immediately.
Errors
| Status | When |
|---|---|
| 400 | Invalid email / password shorter than 8 chars / phoneNumber is not valid E.164 |
| 422 | Email address already registered |
| 409 | Phone number already in use |
Registration is all-or-nothing: the account is created first and the phone attached immediately after, so if the phone write fails (including losing a uniqueness race to a concurrent request) the account is deleted again before the error returns. A 409 therefore leaves nothing behind — the same email can be retried once a free phone number is supplied.
Transactional auth emails
These better-auth flows send real email via the configured SMTP transport (SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS, from address MAIL_FROM). All templates are brand-neutral (no per-store logo/colours) — this is a white-label, single-tenant platform.
| Trigger | Endpoint(s) | Email sent | Notes |
|---|---|---|---|
| Email verification | POST /auth/send-verification-email, GET /auth/verify-email?token=… | "Verify your email" | Auto-sent on sign-up, but not required to log in (requireEmailVerification is off). Clicking the link also signs the user in (autoSignInAfterVerification). |
| Password reset | POST /auth/request-password-reset, POST /auth/reset-password | "Reset your password", then "Your password was changed" | The reset-link email is followed by a security-notice email once the reset completes (onPasswordReset). |
| Magic link | POST /auth/sign-in/magic-link | "Sign in to your account" | Passwordless sign-in link (expires in ~10 min). |
| Change email | POST /auth/change-email | "Confirm your email change", then "Confirm your new email address" | Two hops when the current address is verified: the current address approves first, which mails the new address, and the change applies only once that second link is followed. If the current address is unverified there is a single hop, sent straight to the new address. |
| Delete account | POST /auth/delete-user, GET /auth/delete-user/callback?token=… | "Confirm your account deletion" | Self-service deletion is gated behind the emailed confirmation link. |
| Welcome | (none — fired on user creation) | "Welcome" | Sent on every new account (storefront sign-up, OAuth, passkey). |
If SMTP is not configured the callbacks fall back to logging the link to the server console, so local development still works without a mail server.
Source: wiring in
apps/api/src/app.module.ts; config inapi-modules/auth/src/auth.config.ts; templates inapi-modules/mailer/src/templates/.
Phone-OTP endpoints (available but not yet enabled)
The better-auth phoneNumber plugin mounts the following endpoints. They exist and accept requests but the SMS sender is currently a stub that only logs — phone-based login is not yet wired to a real SMS provider and is not enabled for production use.
| Method | Path | Purpose |
|---|---|---|
POST | /auth/phone-number/send-otp | Send a one-time code to a phone number |
POST | /auth/phone-number/verify | Verify the code and sign in |
POST | /auth/sign-in/phone-number | Sign in with phone number + password |
Do not surface these flows in the customer app until SMS delivery is wired and the feature is explicitly enabled.
User phone fields
Two fields were added to the user record:
| Field | Type | Notes |
|---|---|---|
phoneNumber | string | null | Unique; null for users created outside the storefront registration path |
phoneNumberVerified | boolean | Set to false at registration; updated to true after successful OTP verification (feature not yet enabled) |
Related
customer— address book CRUD for the authenticated customer. Seestore/customer.md.guest-checkout— anonymous checkout without an account. Seestore/guest-checkout.md.
Webhooks — Shared Inbox and Delivery Archive
Platform-agnostic inbound-webhook plumbing: a claim-and-outcome idempotency inbox any provider module can use, and a 90-day archive of every delivery to /webhooks/*.
Affiliate Module — Storefront
HTTP surface for the customer-facing affiliate plugin. Customers apply to join, generate trackable referral links, see their commission balance + payout history, and (anonymous…