anolilab/lunora/lunora-setup-auth
Claude Code and agent skill by anolilab/lunora
- community
Checking that the SKILL.md file is still in the repository.
Adds authentication to a Lunora app with the `auth` registry item (better-auth via `@lunora/auth`, users and sessions in D1). Covers email/password sign-up and sign-in, Clerk or Auth0 OAuth, magic link, email OTP, copy-in auth screens (`lunora add auth-ui`), wiring `/api/auth/*` and `resolveIdentity` into the Worker, and gating functions on `ctx.auth.userId`. Use when the user asks to "add auth", "add login/sign-in/sign-up", "protect this query", or "add Clerk/Auth0/magic link/OTP", wants to keep an external identity provider (Clerk, WorkOS, Auth0, Supabase, any OIDC JWT) as the session owner, runs `lunora add auth` or `lunora registry add auth`, edits `lunora/auth/index.ts`, or `ctx.auth.userId` is unexpectedly `null`.
Install
npx vskill@latest install anolilab/lunora/lunora-setup-authSKILL.md
Lunora Setup Auth
The auth registry item is built on @lunora/auth, a thin wrapper over better-auth. Users and sessions are stored in D1. Lunora's SessionDO is a separate TTL'd token store that @lunora/auth never calls, so don't wire it for auth. If you need auth state in a Durable Object instead of D1, use LunoraAuthDO / .auth({ namespace, internalSecret }).
If the project has no Lunora backend yet, start with lunora-quickstart.
Already have an identity provider? Skip better-auth
If the app's users sign in through Clerk, WorkOS AuthKit, Auth0, Supabase or any OIDC issuer, and that provider should keep owning the session, don't add the auth item. Verify the provider's JWT instead with @lunora/jwt-auth:
On the client, create the LunoraClient with authTokenOnUpgrade: true and, once signed in, call setAuthTokenProvider(client, getToken, { subject: userId }) from lunorash/client. It refreshes before exp and on onTokenExpired; map the provider's "signed out" error (WorkOS LoginRequiredError, Auth0 login_required) to null. authTokenOnUpgrade is what authenticates live-query sockets: the browser offers the bearer as a Sec-WebSocket-Protocol entry. Without it, a WorkOS or Auth0 socket, or a cross-origin Clerk one, is anonymous.
Org claims arrive as orgId with the org role (orgRole for Clerk, role / orgRoles for WorkOS); no preset sets the app-wide roles RLS reads, since any Clerk user can create an organization and be its admin. Check the org role together with orgId in a policy. CLERK_AUTHORIZED_PARTIES is required. Full guide: /docs/concepts/external-auth, with runnable apps in examples/clerk-auth and examples/workos-auth.
The rest of this skill covers the better-auth path.
Step 1: Add the item
Then run pnpm install. The item does the following:
- Adds
@lunora/auth,@lunora/mailand@lunora/servertopackage.json. - Copies
lunora/auth/index.tsinto the project. The project owns this file and can edit it. It exportsbuildAuth(env),getAuth(env)(memoized per isolate) andmountAuth(env, request), which routes/api/auth/*and returnsundefinedfor any other path. The scaffold turns on email/password withrequireEmailVerification: trueand sends verification and reset mail through@lunora/mail. It also adds theuiConfig()plugin. - Adds a D1
DBbinding towrangler.jsonc. Thedatabase_idis a placeholder. - Writes
BETTER_AUTH_SECRET,BETTER_AUTH_URLandMAIL_FROMto.dev.vars.
Step 2: Database and env vars
| Var | Secret | Notes | |
|---|---|---|---|
BETTER_AUTH_SECRET | yes | At least 32 chars (openssl rand -base64 32). Production: wrangler secret put. | |
BETTER_AUTH_URL | no | Public base URL. better-auth uses it for callbacks and cookies. | |
MAIL_FROM | no | Sender for verification and reset mail. |
Don't declare the better-auth tables (user, session, account, verification) in lunora/schema.ts. better-auth owns them in D1. In dev the schema is auto-applied on the first /api/auth/* request. For production, pre-apply it with compileMigrationsSql(auth.options) piped to wrangler d1 execute <db> --remote --file -. The item README has the script. Re-run that migration after adding a plugin that brings its own tables, such as organization or twoFactor.
Step 3: Wire the Worker
There are two parts. /api/auth/* has to reach better-auth. resolveIdentity has to turn the session cookie into ctx.auth. If resolveIdentity is missing, sign-in still works but ctx.auth.userId stays null in every function.
Through the generated `defineApp` builder. This is the default for templates. .auth() does both parts: it builds the instance lazily, runs ensureMigrated, dispatches /api/auth/* and sets resolveIdentity. A Vite-first app ("main": "virtual:lunora/worker") has no entry file, so chain the call in the lunora.config.ts app hook. A hand-written entry (src/server.ts) chains it on defineApp() directly:
Reuse the scaffold's options rather than retyping a subset: a hand-written { emailAndPassword: { enabled: true } } drops requireEmailVerification, sendResetPassword and sendVerificationEmail, so users sign in unverified and no verification or reset mail goes out.
.auth() appears on the builder only after codegen sees @lunora/auth installed, so run lunora codegen first.
Hand-rolled `createWorker` entry. Call the scaffolded mountAuth first in fetch, then hand every other request to the Lunora worker, which resolves the identity:
Step 4: Providers and add-ons (optional)
Each of these items requires the base auth item and installs it if it is missing:
The provider items copy a file into lunora/auth/ but don't register it anywhere. Add the plugin to the plugins array yourself, in buildAuth or in .auth({ options }). For example, plugins: [uiConfig(), clerk(env)] with import { clerk } from "./clerk.js". The other exports are auth0, magicLinkPlugin and emailOtpPlugin. The OAuth callback URL is <BETTER_AUTH_URL>/api/auth/oauth2/callback/<clerk|auth0>.
Step 5: Use the session
In functions
ctx.auth.getIdentity() returns the full session claims, such as email and name. For org or role checks, compose withAuthPlugins(auth) and call the better-auth server API.
In the client
The default is a better-auth cookie session. LunoraClient needs no token, so don't call setToken / setAuthToken for it. Sign in with the better-auth client. Build that client with createLunoraAuthClient, which installs lunoraSessionSync(). That plugin tells every open LunoraClient, including ones in other tabs, to re-resolve the session after sign-in or sign-out. Without it, live queries keep serving the previous user's rows. If you call createAuthClient yourself, add lunoraSessionSync() to its plugins.
A client plugin toggle such as { plugins: { twoFactor: true } } only adds the client half. Register the matching server plugin (twoFactor() from @lunora/auth/plugins) in buildAuth's plugins too, or its endpoints don't exist.
setToken is only for bearer-token setups, such as React Native or a JWT from an external IdP.
Branch on status, not on user === null. status takes one of four values: "unauthenticated", "loading", "authenticated" or "unreachable". The gates in @lunora/vue, @lunora/solid, @lunora/svelte and @lunora/angular use the same four. "unreachable" means a credential is held but the session endpoint failed, for example on an offline reload. The gates treat it as authenticated, yet user can still be null at that point. A UI that reads that null as "signed out" shows a signed-in user the signed-out screen. The full contract is documented on AuthStatus in @lunora/client/auth.
Pitfalls
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingpackages/cli/skills/lunora-setup-auth/SKILL.md
Popularity
- 7-day trend score
- 5
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/anolilab/lunora/lunora-setup-auth)