Admin UI + Passkey Auth Plan
Building a local admin interface with passwordless WebAuthn authentication using Bitwarden's passwordless.dev service.
March 30, 2026Why passwordless.dev
- Bitwarden-backed, free up to 10k users, trustworthy
- Handles the WebAuthn ceremony so we never touch
navigator.credentialsdirectly - Passkeys are stored in Bitwarden - same workflow as everything else
- Right-sized for a personal site: no Clerk overhead, no OAuth dance, no passwords to manage
Auth Stack
- passwordless.dev — WebAuthn registration + assertion
@passwordlessdev/passwordless-client— browser-side SDK (triggers passkey prompt)jose— sign and verify a JWT stored in anhttpOnlycookie- Next.js middleware — protect all
/admin/*routes at the edge
Flow
One-time registration (localhost only)
- Hit
/admin/register(disabled in production) - Server calls passwordless.dev API → gets a registration token
- Client SDK uses that token to trigger the browser passkey creation prompt
- Passkey saved in Bitwarden
- Done — never do this again
Sign in
- Hit
/admin/login - Client SDK triggers passkey assertion (Bitwarden prompt)
- Server verifies the assertion token with passwordless.dev API
- Server signs a JWT (
jose) and sets it as anhttpOnlySecurecookie - Middleware reads cookie on every
/admin/*request — valid JWT = through, missing/invalid = redirect to/admin/login
Sign out
Delete the session cookie.
Routes
/admin → redirect to /admin/content
/admin/login → passkey sign-in page (client component)
/admin/register → one-time passkey registration (disabled in prod)
/admin/content → list all content rows (draft + published + archived)
/admin/content/[slug] → edit title, description, type, status, tags, etc.
Admin UI
The content editor at /admin/content/[slug] needs to update fields in Turso directly — no sync script, no file changes. Fields editable from the UI:
titledescriptionstatus(draft → published → archived)tagstypethumbnailfeatureauthorship_note
Save triggers a Server Action that runs an UPDATE query against Turso. No page reload required.
Security Notes
- Registration route disabled (404) when
NODE_ENV === "production" - Session JWT signed with a secret from env (
ADMIN_JWT_SECRET) — rotate to invalidate all sessions - Cookie is
httpOnly,Secure,SameSite=Lax - Middleware runs at the edge — unauthenticated requests never reach page code
Implementation Order
- Install
@passwordlessdev/passwordless-clientandjose - Add env vars:
PASSWORDLESS_API_KEY,PASSWORDLESS_API_SECRET,ADMIN_JWT_SECRET - Build
/admin/login(client component, calls SDK) - Build Route Handlers:
/api/auth/signin/token,/api/auth/signin/verify,/api/auth/signout - Build middleware for
/admin/* - Build registration flow (localhost only)
- Build
/admin/contentlisting page - Build
/admin/content/[slug]editor with Server Action save