PRD: Authentication & account verification
| Module | User Management | PRD ID | PRD-AUTH-001 |
| Status | Ready to dev | FEAT | AUTH |
| Epic | — | Plane | BANA-1526 |
| Date | 2026-05-25 | Version | v1.0 |
| Packages | @nx/identity | URD | AUTH |
| Surface | Client · Owner/MgrSale · POSBO · Ops | ||
| Owner | Phát Nguyễn | ||
What Authentication & account verification is
User Management is the platform's foundation - every other module trusts the identity and scope it issues. Sign-up and password sign-in already work; this PRD closes the three gaps that remain on top of that foundation: verifying identity before getting to work, setting a first password for an account created on someone's behalf, and blocking sign-in across Client, Sale, and Back Office.
Three gaps closed
Before this increment, a fresh account had not yet proven it owns a phone number or email before the user started using the app. A Client user who forgot their password could self-serve, but an employee whose account was created on their behalf, on Back Office, had no way to set a first password. And Client, Sale, and Back Office serve three very different audiences, but nothing stopped an administrative account from signing into the customer-facing page, or the reverse.
This increment closes all three gaps on top of the existing sign-up / sign-in / password-hashing foundation: an onboarding wizard that treats phone verification as a hard gate, a dedicated route for an owner or manager to set a password for an account created on someone's behalf, and cross-page sign-in blocking by role tier - all enforced on the server, not just in the interface.
One account, from sign-up to first use
| Step | What happens |
|---|---|
| 1. Sign-up | On Client, enter first name, last name, username, password, tick terms acceptance → account, profile, the username identifier, and default settings are created together, and the user lands in the app right away |
| 2. Onboarding | A 4-step wizard follows: phone verification (hard gate) → business creation → plan selection → welcome |
| 3. Daily sign-in | The same identifier field (username, or a verified email/phone) plus a password field, shared across Client, Sale, and Back Office |
| 4. Forgot password | On Client, self-serve a reset through exactly one recovery channel (an email carrying both a 6-digit code and a link, or an SMS OTP) |
| 5. Employee created on your behalf | An owner or manager sends a dedicated link so the employee can set their first password themselves |
Where it's easy to get wrong
Blocking by role tier lives on the server, not the interface
An account with a system role above Owner (Super Admin, Admin, Operator) cannot sign in on Client or Sale; an account at Owner tier and below cannot sign in on Back Office.
Refusing to issue a token happens right at the server. Calling the API directly is blocked too, not just a hidden button in the interface.
1. Goals & Non-Goals
Goals
- Sign-up on Client in one all-or-nothing operation - account, profile, the username identifier, and default settings are created together; the user enters the app immediately, and verification is handled in the following onboarding step.
- A 4-step onboarding wizard right after sign-up, where the phone verification step is a hard gate - the wizard cannot proceed until it passes.
- Username is the platform's primary identifier; phone verification is mandatory after onboarding for users at Owner tier and below; email is always optional.
- Unified sign-in across all 3 apps with username (or a verified email/phone) plus password, returning a scoped session token; when the session expires, the user signs in again.
- Cross-page sign-in blocking by role tier: system roles above Owner (Super Admin, Admin, Operator) cannot sign in on Client or Sale; roles at Owner tier and below cannot sign in on Back Office - enforced from the server.
- Password change while signed in, and self-service forgot-password on Client through exactly one recovery channel (email or SMS OTP, never both at once).
- A dedicated route for an owner or manager to set a password for an employee account created on that employee's behalf.
- Identifier management after onboarding in Account Settings - view the list, add new ones, verify an unverified email or phone.
- The foundation for passwordless phone-OTP sign-in and opt-in two-factor enforcement.
Non-Goals
- Remote session management and manual revocation.
- User invitation via email or phone.
- Detailed login history / audit logging.
- Self-service forgot-password on Back Office or Sale - neither page has a forgot-password screen; an employee who forgets their password there needs an owner or manager to send a password-set request through the account-created-on-your-behalf route (FR-015).
2. Success Metrics
| Metric | Target / signal |
|---|---|
| Onboarding completion | Share of new accounts that clear the phone-verification gate and finish all 4 wizard steps trends up |
| Self-service recovery | Number of forgotten passwords resolved via email or SMS OTP, without operations-team involvement, trends up |
| Sign-in integrity by page | Zero sign-ins that slip past role-tier blocking - an admin reaching Client/Sale, or Owner-tier and below reaching Back Office |
| Credential safety | 100% of stored passwords hashed; no plain-text found in logs |
| Code reliability | Delivery success rate for OTPs (SMS/email); expired/max-attempt cases handled gracefully |
| Remember-me | Share of long-lived sessions (remember-me ticked) that skip re-login across app opens trends up |
3. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Owner | Sign up, complete the onboarding wizard, secure and self-recover their own account |
| Cashier / Employee | Sign in with their own credentials on Sale or Client, change their password, get a password set on their behalf by an owner or manager when needed |
| Owner / Manager | Send a password-set request for an employee account created on that employee's behalf; manage their own identifiers in Account Settings |
| Super Admin / Admin / Operator | Sign in on Back Office; cannot sign in on Client or Sale with an administrative account |
| Customer | Sign up, complete mandatory phone verification through the wizard, self-recover a forgotten password |
Core scenario: sign up on Client (account + profile + username, straight into the app) → 4-step onboarding wizard, phone verification is a hard gate → create the business, Owner role assigned → sign in on Client/Sale/Back Office with a verified identifier, receive a scoped session token → change password, or if forgotten, self-serve a reset via email/SMS OTP → if an employee account created on someone's behalf has never set a password, open the link the owner/manager sent to set one for the first time → after onboarding, add and verify further identifiers in Account Settings.
4. User Stories
| # | As a | I want | So that |
|---|---|---|---|
| 01 | new user | to sign up with a username and password on Client in one step | my account, profile, and username identifier are created together and I get into the app right away |
| 02 | new user | the onboarding wizard to walk me through phone verification before creating my business | my account has a trustworthy phone number from the start |
| 03 | user | to sign in with my username and password on whichever page (Client, Sale, Back Office) my account is allowed to use | I receive a session token carrying my exact role and scope |
| 04 | user | to tick Remember me | I don't have to sign in again every time I open the app |
| 05 | user | to change my password after confirming my current one | I can rotate credentials safely |
| 06 | user who forgot my password on Client | to request a reset, verify a code or click a link, and set a new password | I can recover access without support |
| 07 | owner | to send a password-set request for an employee account I just created | that employee can set a first password themselves through a dedicated link |
| 08 | internal administrator | my Back Office account to be unable to sign into Client or Sale | administrative access stays separate from customer or salesperson access |
| 09 | signed-in user | to add and verify a new email or phone in Account Settings | I have more ways to sign in and recover |
5. Functional Requirements
| # | Requirement | Status | URD ref |
|---|---|---|---|
FR-001 | Sign up on Client with first name, last name, username and password. • The referral code is optional, the terms-acceptance tick is mandatory. • Sign-up is one single step that creates the account and profile together; a mid-way failure creates nothing. | ✅ | URD-AUTH-001..003 |
FR-002 | After sign-up the user lands in the app right away, and a 4-step onboarding wizard follows. • The 4 steps run in order: phone verification → business creation → plan selection → welcome. • The phone-verification step is a hard gate: enter the number, receive an SMS OTP, enter the code. • Edit-number and resend-code buttons are available at this step. | ✅ | URD-AUTH-009 |
FR-003 | In the onboarding wizard, the user may add one optional email. • Verify the email by clicking the link sent by mail. • Skipping this step does not block onboarding. | ✅ | URD-AUTH-008 |
FR-004 | Username is the platform's primary identifier. • Signing up and signing in with username always works, independent of email or phone. • After onboarding, users at Owner tier and below must keep at least one verified phone number (SMS via the VNPAY provider). • Email is always optional. | ✅ | URD-AUTH-015..016 |
FR-005 | Sign-in on Client, Sale and Back Office uses the same form: an identifier field (username, or a verified email/phone) and a password field. Once signed in, the user sees exactly their part per role and organizer/merchant. | ✅ | URD-AUTH-004..005 |
FR-006 | A standard sign-in session lasts 24 hours. • When it expires, the app signs the user out to the sign-in screen, and they must sign in again. • There is no silent extension mechanism. | ✅ | URD-AUTH-005 |
FR-007 | The sign-in screen has a Remember-me checkbox: ticked keeps the session for 30 days, unticked applies the standard 24 hours. | 🚧 | URD-AUTH-005 |
FR-008 | Accounts with a system role above Owner (Super Admin, Admin, Operator) cannot sign in on Client or Sale. • The system refuses right at sign-in, not just by hiding things in the interface, with a clear message. • To act as a customer, create a separate account. | 🚧 | URD-AUTH-020 |
FR-009 | Accounts at Owner tier and below cannot sign in on Back Office. The system refuses right at sign-in, with a clear message. | 🚧 | URD-AUTH-021 |
FR-010 | The system records each account's most recent sign-in time. | ✅ | URD-AUTH-012 |
FR-011 | A signed-in user changes their password by entering the current password, the new one, and its confirmation. On success, the app signs them out and they sign in again with the new password. | ✅ | URD-AUTH-007 |
FR-012 | Passwords are stored only as a one-way hash using a password-grade algorithm. Plain text is never stored or logged. | ✅ | URD-AUTH-006 |
FR-013 | In Account Settings, the Profile section manages the user's identifiers. • View the list of your emails and phone numbers with their verification status. • Add a new identifier. • Click an unverified identifier to verify it: email via link or code, phone via SMS OTP. | 🚧 | URD-AUTH-011 |
FR-014 | On Client, a user resets a forgotten password themselves. • The system sends ONE email carrying both a 6-digit code and a link. • Entering the code or clicking the link both lead to the same set-new-password step. • After setting the new password, the user signs in again. | ✅ | URD-AUTH-010 · URD-AUTH-017 |
FR-015 | An owner or manager sends a password-set request for an employee account created on that employee's behalf. • The recipient clicks the delivered link to set their first password. • Distinct from the self-service forgot-password flow in FR-014. | ✅ | URD-AUTH-010 |
FR-016 | Every recovery or activation message goes through exactly ONE channel. • Accounts with a verified email receive it by email, otherwise by SMS OTP to the verified phone. • The two channels are never sent together. | 🔶 | URD-AUTH-018 |
FR-017 | Sign in with an SMS OTP instead of a password, for accounts with a verified phone number. | 🚧 | URD-AUTH-019 |
FR-018 | A user can enable two-factor authentication on their account. When enabled, password sign-in requires one more verification step. | 🚧 | URD-AUTH-013 |
5.1 Acceptance Criteria
- Sign up on Client with First name, Last name, username, password, and the terms-acceptance checkbox ticked → account, profile, the username identifier, and default settings are created together, and the user lands straight in the app.
- Sign up without ticking the terms-acceptance checkbox, or with a username that already exists → rejected, nothing is created.
- Right after sign-up, step 1 of the onboarding wizard asks for a phone number and an SMS OTP code → until it's verified, the wizard cannot advance to step 2, creating the business.
- At the phone-verification step, enter a wrong number then click edit-number → the old number is cleared, a new one can be entered and re-verified from scratch.
- At the phone-verification step, click resend code → a new SMS OTP is sent.
- At the phone-verification step, provide an optional email and skip its verification → the wizard still allows moving on to the create-business step.
- Click the email-verification link sent in the mail during onboarding → the email is marked verified.
- Complete all 4 wizard steps → the business is created, the user is assigned the Owner role, and lands on the welcome screen.
- Sign in on Client, Sale, or Back Office with the correct username and password and a verified identifier → receive a session token carrying roles and organizer/merchant scope.
- Sign in with a verified email or phone (instead of username) plus the correct password → sign-in still succeeds.
- Sign in with a wrong password, or an unverified identifier → rejected with a reason.
- The session token expires while the app is in use → the app signs the user out to the sign-in screen; signing in again resumes work.
- Create a new merchant → the new merchant is usable right away, no sign-out/sign-in needed.
- Sign in with Remember me ticked, close the app and reopen it within the extended window → still signed in, no re-login required.
- Sign in without ticking Remember me → the session expires on the standard schedule.
- A Super Admin, Admin, or Operator account attempts to sign in on Client or Sale → the server refuses to issue a token, and the sign-in screen shows the reason.
- An Owner, Cashier, Employee, or Customer account attempts to sign in on Back Office → the server refuses to issue a token, with a clear message.
- An internal staff member wants to act as a customer → they must create a separate account for that purpose, not reuse the administrative one.
- A successful sign-in → the account's last-sign-in timestamp is updated.
- While signed in, change the password with the correct current password and a matching new password and confirmation → the password changes, the account signs out automatically, and re-login with the new password is required.
- Change password with the wrong current password → rejected, the old password stays unchanged.
- Inspect any log or response touching a password → plain text is never present, only the hashed value at the storage layer.
- In Account Settings, open the Profile section → see the current identifier list with verification-status labels; add a new email or phone → the new identifier appears unverified, click it to open and complete its verify flow.
- On Client, request a password reset for an account with a verified email → receive a single email carrying both a 6-digit code and a reset link; entering the code or clicking the link both lead to the same set-new-password screen.
- Finish setting a new password via the forgot-password flow → the password is stored hashed, the response never carries a session token, and re-login with the new password is required.
- An owner creates an employee account then sends a password-set request for it → the employee receives a dedicated link and clicks it to set a password for the first time at the set-password-for-an-account-created-on-your-behalf route.
- An account with a verified email requests recovery or sign-in activation → only an email is sent, no SMS is sent alongside it.
- An account without a verified email requests recovery or sign-in activation → an OTP is sent via SMS to a verified phone number.
- A user with a verified phone requests an OTP sign-in → receives a code via SMS, enters the correct, unexpired code → receives a session token without ever entering a password.
- Request an OTP sign-in but submit a wrong or expired code → rejected with a reason.
- Turn on two-factor enforcement for an account → the next password sign-in requires an additional second-factor step before a session token is issued.
6. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Data integrity | Sign-up is all-or-nothing - if any step fails, nothing is created (account, profile, identifier, settings) |
| Credential safety | Passwords hashed before storage; both reset and set-password flows write a hashed password; the forgot-password and password-set-for-an-account-created-on-your-behalf responses never carry a session token |
| Tenancy & authz | The session token carries roles and organizer/merchant scope; every operation except sign-up/sign-in/OTP requires authentication |
| Blocking by role tier | Refusing to issue a token for a given app context (Client/Sale/Back Office) happens on the server, not just in the interface - calling the API directly is blocked too |
| OTP robustness | Every one-time code (email, SMS) expires and caps retry attempts; expired or max-attempt cases return explicit errors |
| Recovery channel | A recovery or sign-in-activation flow sends exactly one channel (email or SMS OTP), never both at once |
| Session | Exactly one session token, no refresh token; expiry forces sign-out; 24-hour standard / 30-day duration when Remember-me is ticked |
| Configurability | Mail content and SMS templates are driven by centrally stored, configurable settings, not fixed strings |
| i18n | User-facing labels, statuses, and mail/SMS content are bilingual (English / Vietnamese) |
7. UX & Flows
Key screens: the sign-up screen and the 4-step onboarding wizard on Client; a shared sign-in layout across Client, Sale, and Back Office, differing only in the role-tier blocking message; the Profile section in Account Settings for post-onboarding identifier management; the forgot-password screen on Client; the dedicated route for setting a password on an account created on your behalf.
8. Data & Domain
| Entity | Role |
|---|---|
| User account | The authenticated identity that owns credentials, identifiers, profile, and role |
| Password credential | The hashed password secret used to authenticate |
| Login identifier | A login value (username / email / phone), globally unique within its type, carrying a verified flag |
| Sign-in session | A single session token; the Remember-me flag decides its duration (24 hours / 30 days) |
| One-time-code session | Short-lived state backing verification, recovery, and OTP sign-in (request → submit, with expiry and attempt caps, tied to one channel - email or SMS) |
| Recovery request | A password-reset request, either self-initiated (forgot-password) or initiated on someone's behalf (owner/manager sending it for an employee account created for them) |
| Role tier | The role grouping that decides which page a user can sign into - above Owner (Super Admin, Admin, Operator) only reaches Back Office; Owner tier and below (Owner, Cashier, Employee, Customer) only reach Client/Sale |
Conceptual only - full data model and rules in the identity domain model.
9. Dependencies & Assumptions
Depends on
| # | Feature | What it depends on |
|---|---|---|
| 01 | User Account (URD-USR) | the users, identifiers, and verified lifecycle this feature drives. |
| 02 | Roles & scoping (URD-ROLE - Permissions) | sign-up assigns the default GUEST role; sign-in resolves roles and scope into the token; the eight fixed roles are the basis for role-tier blocking. |
| 03 | Employee Management (URD-EMP) | the employee accounts an owner or manager creates on someone's behalf, the target of the password-set-for-an-account-created-on-your-behalf flow. |
| 04 | Commerce | creating the business at wizard step 2 grants the Owner role. |
| 05 | Delivery infrastructure | the VNPAY SMS provider for phone OTPs; mail delivery for codes and email links; the Client/Sale/Back Office apps for the sign-in, onboarding, verify, and reset screens. |
Assumptions
| # | Assumption | What breaks if wrong |
|---|---|---|
| 01 | A supplied phone number or email is reachable so a code or link can be delivered. | The code or link never arrives, and the user gets stuck mid-verification, mid-forgot-password, or mid-password-set-for-an-account-created-on-your-behalf. |
| 02 | Mail configurations and SMS templates are in place before any verification/recovery notification is sent. | Notification delivery fails or carries the wrong content, blocking the entire verification or recovery flow it belongs to. |
| 03 | Username is required and automatically considered verified; email and phone start unverified. | An identifier that has not proven ownership gets treated as verified, opening the door to impersonated authentication. |
10. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - see URD feature catalog |
| Rollout | All users across all 3 apps; no feature flag |
| Migration | No new data migration needed - reuses the existing account, identifier, and configuration tables |
| Launch criteria | Onboarding-wizard phone verification end-to-end; sign-in correctly blocked by role tier on all 3 apps; forgot-password and password-set-for-an-account-created-on-your-behalf end-to-end; passwords always stored hashed |
| Monitoring | Wizard completion rate, OTP delivery/completion rate by channel, count of sign-ins blocked by role tier, sign-in failure reasons |
References
- URD: User Management - Authentication - AUTH requirements + acceptance
- Builds on: User Account · Roles & scoping - Permissions URD · Employee Management
- Related PRD: Employee Management · User Account & Configuration · User Identifiers & Configuration
- Module: User Management - URD
- Developer: @nx/identity · domain model
Risks & Open Questions
| # | Risk / question | Mitigation / status |
|---|---|---|
| 01 | Partial sign-up could leave orphaned account fragments | Sign-up is all-or-nothing; any failure rolls everything back |
| 02 | Code brute-force / replay | Codes expire and cap attempts; expired/max-attempt errors are explicit |
| 03 | Reset response leaking a session token | The session token is always stripped from both the forgot-password and password-set-for-an-account-created-on-your-behalf responses |
| 04 | A user has no email when they forget their password | The recovery channel falls back to SMS OTP to a verified phone number, with no hard dependency on email |
| 05 | Calling the API directly bypasses interface-level blocking | Refusing to issue a token for an app context happens on the server, not just on the sign-in screen |
| 06 | An employee account created on someone's behalf that has never set a password could be locked out | The password-set-for-an-account-created-on-your-behalf route lets an owner or manager resend the request at any time |
| 07 | Two-factor enforcement and phone-OTP sign-in are two sign-in paths that run parallel to a password | Both go through the same scoped session-token issuance mechanism, so neither opens a permission shortcut |
FAQ
| # | Question | Answer |
|---|---|---|
| 01 | Do I have to verify anything right after signing up? | No - you land in the app right away after sign-up. The onboarding wizard runs next, and phone verification is a hard gate of the wizard, not of sign-up itself. |
| 02 | Can I sign in with an email or phone, or only a username? | All three - the identifier field on the sign-in screen accepts username, email, or phone, as long as that identifier is verified. |
| 03 | What happens if I forget my password? | On Client, you self-request a reset; the system sends a single email with both a 6-digit code and a link (if you have a verified email), or an SMS OTP (if you don't). Back Office and Sale don't have this screen - an employee there needs an owner or manager to send a password-set request on their behalf. |
| 04 | How is the password-set-for-an-account-created-on-your-behalf route different from forgot-password? | Forgot-password is self-initiated by the account owner. The password-set-for-an-account-created-on-your-behalf route is initiated by an owner or manager for an employee they just created - the recipient clicks the link to set a password for the first time. |
| 05 | Why can't an administrative account sign into Client? | Because roles above Owner (Super Admin, Admin, Operator) are scoped to Back Office only; to act as a customer, a separate account must be created for that purpose. |
| 06 | How does Remember-me work? | Tick the checkbox at sign-in and the session token is issued with a 30-day duration instead of the standard 24 hours. |