Bolt prompt to add JWT authentication
Rolling JWT auth inside Bolt has one trap: bcrypt's native bindings will not compile in a WebContainer, and this prompt routes around it with bcryptjs from the start. The result is a complete token lifecycle, register, login, refresh rotation, logout, plus a curl script that proves each transition from the terminal.
Last updated
Add JWT auth to the Express API in this Bolt project. Use jsonwebtoken and bcryptjs, not bcrypt, because native bindings will not compile inside the WebContainer.
Endpoints in server/auth.ts:
- POST /api/auth/register: email plus password, hash with bcryptjs at 10 rounds, reject passwords under 8 characters with 422 and a field-level error body.
- POST /api/auth/login: verify credentials, return a 15 minute access token in the JSON body and a 7 day refresh token in an httpOnly cookie.
- POST /api/auth/refresh: read the cookie, rotate the refresh token, return a new access token. A reused old refresh token invalidates the whole session family.
- POST /api/auth/logout: clear the cookie and revoke the refresh token server-side.
Middleware requireAuth in server/middleware.ts: read the Authorization Bearer header, verify signature and expiry, attach req.user, and return 401 with { error: "token_expired" } distinct from { error: "invalid_token" } so the client knows when to refresh.
Secrets: JWT_SECRET comes from .env. Generate a placeholder value and remind me to change it, never hardcode it.
Apply the middleware to the existing /api/{{resource}} routes, leaving GET public. Persist users and refresh tokens in whatever store the project already uses, adding a users table if none exists.
Acceptance: write scripts/auth-check.sh with curl calls that register, log in, hit a protected route, tamper with one token character and get 401, refresh successfully, then log out and prove the refresh path is dead.Customize it
Runs in your browser. Nothing you type here is sent anywhere.
Add JWT auth to the Express API in this Bolt project. Use jsonwebtoken and bcryptjs, not bcrypt, because native bindings will not compile inside the WebContainer.
Endpoints in server/auth.ts:
- POST /api/auth/register: email plus password, hash with bcryptjs at 10 rounds, reject passwords under 8 characters with 422 and a field-level error body.
- POST /api/auth/login: verify credentials, return a 15 minute access token in the JSON body and a 7 day refresh token in an httpOnly cookie.
- POST /api/auth/refresh: read the cookie, rotate the refresh token, return a new access token. A reused old refresh token invalidates the whole session family.
- POST /api/auth/logout: clear the cookie and revoke the refresh token server-side.
Middleware requireAuth in server/middleware.ts: read the Authorization Bearer header, verify signature and expiry, attach req.user, and return 401 with { error: "token_expired" } distinct from { error: "invalid_token" } so the client knows when to refresh.
Secrets: JWT_SECRET comes from .env. Generate a placeholder value and remind me to change it, never hardcode it.
Apply the middleware to the existing /api/users routes, leaving GET public. Persist users and refresh tokens in whatever store the project already uses, adding a users table if none exists.
Acceptance: write scripts/auth-check.sh with curl calls that register, log in, hit a protected route, tamper with one token character and get 401, refresh successfully, then log out and prove the refresh path is dead.Same task in other tools
Questions about this prompt
Why an access token in JSON but a refresh token in a cookie?
The access token is short-lived, so holding it in client memory is an acceptable risk. The refresh token is the crown jewel, and the httpOnly cookie keeps it away from any script. In Bolt's preview both sides share an origin, so the cookie flows without CORS ceremony.
Should I just use Supabase auth instead of hand-rolling this?
If your project already connects Supabase through Bolt, yes, its auth handles rotation and storage for you and this prompt becomes unnecessary. Hand-rolled JWT earns its place when you need custom claims, a user store outside Supabase, or you want to see exactly what the middleware trusts.
Tokens verify fine, but every request after a preview restart returns 401. Why?
The refresh token store was in memory, and the WebContainer restart emptied it, so rotation fails and sessions die. Persist refresh tokens to your database rather than a Map. While you are there, confirm JWT_SECRET comes from .env, because a secret regenerated on each boot also invalidates everything.