Skip to content
Why My Blog API Requires Login Even to Read

10 July 2026

Why My Blog API Requires Login Even to Read

security

Every other read endpoint in dev-hub is public: GET /api/projects returns data to anyone, no token required. GET /api/posts is the one exception, and the exception is deliberate.

The asymmetry

A Project has no draft state — everything in that table is meant to be seen. A Post does: publishedAt: null is a real, common state, and its title and content might be half-written, embarrassing, or simply not ready. If GET /api/posts/[id] were public, anyone who could guess or enumerate an id — and cuid()s are not secret, just hard to guess — could read a draft before it's meant to exist.

Where the line actually sits

The fix isn't clever: requireAuth() at the top of both GET and POST in /api/posts/route.ts. The public /blog and /blog/[slug] pages never call this endpoint at all — they query Prisma directly through getPublishedPosts() and getPublishedPostBySlug(), which hard-code the publishedAt <= now() filter. Only /dashboard/blog calls the API, and only an authenticated session reaches /dashboard/blog in the first place.

The general shape of the decision

The rule that fell out of this: an authenticated-only surface and a public surface should not share a code path if one of them can return a draft. It would have been possible to keep GET /api/posts public and filter drafts out for anonymous callers, but that puts the security boundary inside a conditional instead of at the door — and a conditional is exactly the kind of thing a future edit forgets.