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

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.
← All postsRoadmapCommitmentsChat with Baily →