I used to think authentication was a finished feature once a user could sign up, log in, and receive a token. That was a reasonable place to start in a bootcamp project. It is not a reasonable place to stop in a product.
Building Mosaic Kitchen and QualiSapio made the difference concrete. A failed auth boundary no longer exposes a demo post. It may expose a household profile or an unpublished manuscript, consume somebody’s paid allowance, or run an expensive model call under the wrong account.
AI did not invent authentication. It made every authentication mistake more expensive.
The important question is not “sessions or JWT?” It is: what happens when a credential is stolen, an account is deleted, or an AI action costs real money?
First: authentication is not authorization
The words are often collapsed into “auth”, but they answer different questions. A product needs both.
Authentication
Who are you? A password, Google account, ORCID iD, session cookie, or token helps the system establish an identity.
Authorization
What may this identity read, change, run, or pay for? This is where ownership checks, plan entitlements, quotas, and role boundaries live.
Vibe coding makes software engineers more important here
AI can scaffold a sign-in form, wire an auth library, and produce a route guard in minutes. That speed is useful. But generated code does not own the threat model. It does not decide which data is sensitive, where trust changes hands, how a stolen session should be contained, or whether a model call must be refused before it starts spending money.
One important role of a software engineer in the vibe-coding era is to design those boundaries and verify that the generated implementation really enforces them. The work is less about typing every line and more about defining invariants: user A can never read user B’s record; logout revokes access; a provider email change cannot merge identities by accident; no paid AI action begins before session, ownership, entitlement, and quota checks all pass.
Vibe coding amplifies architecture. With a sound security model, it helps an engineer implement and test faster. Without one, it can produce a polished login experience around a weak boundary. The form may look finished while the system behind it is not.
Three projects, three different answers
These implementations are not a ladder from “bad” to “good”. They belong to different stages, architectures, and threat models. But the differences show why auth choices should change as the product changes.
Acebook: JWT in localStorage
Acebook uses a familiar teaching-project flow: submit an email or username and password, receive a signed JWT from the Express API, keep it in browser localStorage, and send it back in the Authorization header. The API verifies the signature and reads the user id from the token’s sub claim.
The current repository also compares the submitted password directly with the value stored on the user record. In other words, the password path is not production-safe: it lacks password hashing. A token in localStorage is also readable by JavaScript, so an XSS bug can turn into credential theft.
That does not make JWT inherently wrong. It means this particular implementation was useful for learning request authentication, but it was not designed for sensitive production data, revocation, social sign-in, or paid access.
- JWT signed with a server-side secret
- Token stored in browser localStorage
- Bearer token checked by Express middleware
- Direct password comparison in the current code
Why not reuse it? Mosaic Kitchen and QualiSapio need revocable sessions, safer credential storage, stronger browser-cookie boundaries, and reliable ownership and quota checks. Copying the Acebook flow would copy its threat model too.
Mosaic Kitchen: explicit, server-side sessions
Mosaic Kitchen uses hand-written, PostgreSQL-backed sessions. Passwords are hashed with bcrypt at cost 12. A session id is generated from 32 cryptographically random bytes, stored in PostgreSQL, and sent in an HttpOnly cookie. In production the cookie is Secure; SameSite defaults to Lax.
Logout and account deletion can revoke access immediately by deleting a session row. Expiry is checked in SQL. Login rate limits and a dummy bcrypt comparison reduce account-enumeration signals. Google sign-in uses OpenID Connect with PKCE, state, and nonce, and identities are keyed by provider plus subject—not by an email address that may change.
The choice is deliberately explicit. Mosaic has a separate Express API, a planned iOS client, household data, Stripe entitlements, and per-user AI quotas. The server needs to know not only that a request is signed in, but whose pantry row it may read and whose model budget it may spend.
- PostgreSQL-backed, revocable sessions
- bcrypt cost 12 and timing-enumeration defence
- 256-bit random ids in HttpOnly cookies
- Google OIDC with PKCE, state, and nonce
The trade-off is maintenance. Hand-written auth must keep earning trust. Mosaic still documents missing email verification and password reset rather than pretending the system is finished.
QualiSapio: Better Auth with research identities
QualiSapio uses Better Auth with PostgreSQL-backed sessions. It supports email and password, Google, and ORCID. ORCID runs through OpenID Connect with PKCE; the application keeps the researcher profile separate from provider account records.
A lightweight proxy can redirect obvious unauthenticated visits, but cookie presence is not treated as proof. Protected server routes validate the session authoritatively, and the review API performs the same server-side check before accepting a manuscript or reserving review allowance.
Here a framework is the more honest choice. A single Next.js application needs multiple identity providers, account linking, session cookies, and researcher-specific identity mapping. Rebuilding all of that would create security-sensitive code without creating a better peer-review product.
- Better Auth with PostgreSQL sessions
- Email/password, Google, and ORCID
- ORCID OpenID Connect with PKCE
- Authoritative checks on server routes and APIs
Why not use Mosaic’s custom layer? The architecture and identity problem differ. QualiSapio benefits more from a maintained auth framework; Mosaic benefits from an explicit API-level session model. Shared principles matter more than shared code.
Why AI raises the stakes
An AI feature is not just another button behind a login. It can cross privacy, permission, and spending boundaries in one request.
Private inputs are often the product
A household profile reveals routines, preferences, and constraints. A manuscript may contain unpublished research. Authentication is the front door; row-level ownership checks are the locks on every room.
An authenticated action can spend money
Model calls have variable cost. The user identity must be tied to quotas, subscriptions, reservations, and spend ceilings before work begins—not inferred after the bill arrives.
Agents can do more than return text
As systems gain tools, credentials, or write access, a stolen session can trigger actions rather than merely reveal a page. Authentication needs narrow authorization and server-side policy around it.
Deletion and logout must mean something
Users reasonably expect “log out everywhere” or “delete my account” to end access. Revocable server-side sessions make that statement enforceable immediately.
So which auth is the right one?
There is no universal winner. JWT can be appropriate for stateless service-to-service access or carefully designed client flows. Server-side sessions make revocation and browser applications easier to reason about. A framework can remove dangerous reinvention when OAuth, account linking, and provider edge cases arrive.
The decision should start with failure, not fashion: Where is the credential stored? Can it be revoked? What does a stolen session unlock? Is every data query scoped to the authenticated owner? Does the API re-check permission before an expensive action? What happens when a provider changes an email address?
Mosaic Kitchen and QualiSapio do not reuse Acebook’s auth because the products are no longer exercises in making a token pass through a stack. They hold real data, connect external identity providers, meter paid resources, and need logout and deletion to be true—not just visible in the interface.
The checklist I now use
- Hash passwords with a deliberately slow password hash; never store or compare plaintext.
- Prefer HttpOnly, Secure cookies for browser sessions and choose SameSite from the real deployment topology.
- Validate sessions on the server. Cookie presence is only a routing hint.
- Key OAuth identities by provider and stable subject, not email alone.
- Enforce ownership in data access and entitlements before model calls.
- Design revocation, expiry, rate limits, account deletion, and auditability from the start.
- Write down the gaps. “Not implemented” is safer than an interface that implies protection that does not exist.
The part of auth a user sees is small: a form, a Google button, perhaps an ORCID option. The part that earns trust is mostly invisible—cookie flags, database constraints, ownership clauses, expiry checks, rate limits, and the refusal to run an AI action when identity or permission is uncertain.
That is why auth matters more in the AI era. Not because the old rules disappeared, but because more now sits behind the boundary.