ADR-004: Guest-first authentication with bcrypt and JWT¶
Status: Accepted
Quality requirements addressed: QR-004
Context¶
MVP v3 adds a personal account: a profile card, one saved cosmetic bag, per-product feedback, product replacement, and a result tracker. All of these need to identify the user and store personal data. The customer's brief (ТЗ §7.4) marks authentication as low priority ("не тратить много времени") and says email or phone are both acceptable, while asking that a real database back the data. Two hard constraints shape the design:
- The core value flow — the questionnaire and
POST /recommend— must stay fully usable by anonymous visitors. Accounts are an addition, not a gate. - The team is small and wants no third-party dependency (SMS/email provider) on the critical auth path for the trial.
Decision¶
Introduce a thin, guest-first authentication layer:
- Guest-first. The questionnaire and
POST /recommendstay open to anonymous users./recommendtakes an optional auth dependency: a signed-in request additionally persists the profile and the saved bag, while a guest (or an invalid/expired token) is served normally and persists nothing. All account endpoints (/auth/me,/profile,/care,/tracker,/account…) require authentication. - Email + password. Email is the unique, case-insensitive login identifier. Passwords are hashed with bcrypt and never stored or returned in plain text. A phone number may be collected but is not used for authentication (no SMS/OTP), keeping the customer's "keep it light" guidance.
- Stateless JWT. Login and registration issue a JWT (HS256, 7-day
expiry) carried as a
Bearertoken; a reusableget_current_userdependency validates it. The signing secret isJWT_SECRET, supplied via the environment (dev-only default in config). - User data in MongoDB (ADR-002). One
usersdocument per user (with the profile snapshot embedded), onecaredocument (the single saved bag), and onetrackerdocument, each keyed by the user id. A unique index onusers.emailenforces identity at the database level. - Deliberately out of scope for the trial: OAuth/social login, refresh tokens, server-side sessions, and email-based password reset. Password reset needs a transactional email service and is deferred (PBI-503); until it ships, the "Forgot password?" entry point is hidden.
Consequences and tradeoffs¶
- Positive — guest funnel is untouched. Because
/recommendauthentication is optional, the acquisition flow keeps working for anonymous users and the account layer is purely additive. - Confidentiality (QR-004). Passwords exist only as bcrypt hashes and are never returned by any endpoint; this is verified continuously by QRT-004.
- Positive — no external dependency on the auth path. Email+password+JWT needs no SMS/email provider to work, so the trial has no third-party runtime coupling for sign-in.
- Positive — data isolation. Every account document is keyed by user id and
every account endpoint is scoped to the current user, so one user cannot read
another's profile, bag, or tracker. Account deletion cascades across the
users,care, andtrackercollections. - Negative / tradeoff — token lifecycle. A stateless JWT cannot be revoked
before it expires (a 7-day window), and the frontend stores it in
localStorage, which is exposed to XSS. Both are accepted for a low-priority MVP auth layer; a future hardening step could add refresh/rotation or an http-only cookie. - Negative / tradeoff — no self-service recovery yet. Without the email service, a user who forgets their password cannot reset it in the trial; this is a known, documented gap (PBI-503) rather than an insecure direct reset.
- New secret.
JWT_SECRETjoinsMONGODB_URIand the LLM key as an environment secret (see the deployment view and the configuration section of the development process).