Homeowner /account: JWT in HTTP-only cookies, projects, profile
By Jason, Founder · Published · 2 min read · Wave 292
Summary
Wave 292B ships the homeowner-side /account graph: phone-OTP login, JWT signed with HS256 stored in HTTP-only cookie, projects list, profile editor, sign-out flow. 90-day session expiry. Pure-function auth so it runs on Cloudflare Pages without a separate auth service.
Article body
Phase 23.2 of the master plan called for a homeowner OS: a place where a saved scope and the contractor matched to it become a persistent record the homeowner can come back to. Wave 292B is the first stage of that — login, projects, profile, sign-out. The whole thing is built without Supabase. Wave 73 still gates Supabase on operator provisioning, so we shipped a phone-OTP-only path on top of the MariaDB-backed npline_chat backend (Phase 4.I) and signed JWTs in the Cloudflare Pages Function tier.
The login flow is two pages: /login takes a phone number, posts to /api/account/request-otp (the existing Wave 4.I endpoint), and redirects to /login/verify. The verify page accepts a six-digit OTP, posts to /api/account/verify-otp (new Pages Function), and on success the function mints a JWT signed with HS256 over a sort-keys canonical JSON payload and writes it to an HTTP-only Secure SameSite=Lax cookie called askbaily_session with a 90-day Max-Age. The browser cannot read the cookie. The Worker can verify it using Web Crypto on every subsequent request to /api/account/*.
/account shows the homeowner's projects (saved scopes from Wave 4.D), their profile (display name, email if linked, default city), and a sign-out button. /account/projects/[id] shows a single project — its scope summary, the matched contractor, the timeline of events. /account/profile is the edit form. /account/reviews is where the homeowner posts a review of a completed project, gated to only show if there is at least one project with status=completed.
The auth surface is small enough to read end-to-end. lib/auth/jwt.ts is 90 lines of HS256 sign/verify using Web Crypto. lib/auth/cookie.ts is 40 lines of cookie serialize/parse. lib/account/types.ts is the data shape. The four Pages Functions (verify-otp, sign-out, projects/index, profile/index) are all under 80 lines each. We chose this footprint deliberately so a future Supabase migration can re-implement the four endpoints without changing the client.
The 30-day soak began the day Wave 292B shipped. We are looking for: cookie expiry edge cases (24-hour drift on phone clocks), HS256 secret rotation drill (target Q3), and the rate of homeowners who actually come back to /account vs. drop off after a single chat-and-leave. Early data: re-engagement rate after the first save-scope is 18 percent over 14 days, vs. 6 percent before the /account page existed. That is the signal we wanted: a place to come back to is a precondition for becoming a returning user.
Sources & references
Commit attestation
- 9d84ccb58c4b7e3f9d2a8b1c5e7f6a3d2b1c8e9f
- Tests green
- 20
- Files changed
- 12
- Lines added
- 1,080
- Waves
- 292
- Author
- jason
Commit SHAs are from the AskBaily private repository. If you are a journalist, researcher, or regulator and need access to verify, email [email protected].
Frequently asked
- Does AskBaily require an account?
- No. Chatting, browsing pillars, and getting matched to a contractor all work without an account. /account is opt-in for homeowners who want to come back to a saved scope without re-doing the conversation.
- What does the JWT contain?
- Phone hash, profile id, issuance timestamp, expiration timestamp. No PII in the token itself. The phone number lives only in the encrypted MariaDB row keyed off the hash.
- Can I sign out from all devices?
- Sign-out from /account clears the cookie on the active browser. Multi-device revoke ships in a follow-on wave once we wire a server-side session list. For now, rotate your phone number on the profile to invalidate all existing sessions.