@polymorphism-tech/morph-spec 4.17.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CLAUDE.md +36 -0
  2. package/README.md +2 -2
  3. package/bin/task-manager.js +12 -0
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents/architecture/architecture-expert.md +86 -0
  8. package/framework/agents/architecture/morph-spec-architect.md +66 -0
  9. package/framework/agents/architecture/packages-validator.md +77 -0
  10. package/framework/agents/architecture/security-expert.md +67 -0
  11. package/framework/agents/backend/ms-agent-expert.md +1 -1
  12. package/framework/agents/backend/neon-expert.md +9 -27
  13. package/framework/agents/frontend/design-system-validator.md +88 -0
  14. package/framework/agents/frontend/nextjs-component-validator.md +85 -0
  15. package/framework/agents/frontend/{ui-ux-designer.md → ui-designer.md} +31 -0
  16. package/framework/agents/integrations/neon-auth.md +151 -0
  17. package/framework/agents.json +22 -14
  18. package/framework/hooks/claude-code/post-tool-use/dispatch.js +39 -0
  19. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +78 -44
  20. package/framework/hooks/claude-code/post-tool-use/loop-detect.js +103 -0
  21. package/framework/hooks/claude-code/session-start/inject-morph-context.js +21 -2
  22. package/framework/hooks/claude-code/stop/validate-completion.js +48 -1
  23. package/framework/hooks/shared/activity-logger.js +56 -1
  24. package/framework/hooks/shared/build-check.js +79 -0
  25. package/framework/hooks/shared/payload-utils.js +39 -0
  26. package/framework/hooks/shared/session-snapshot.js +105 -0
  27. package/framework/phases.json +1 -1
  28. package/framework/rules/neon-auth-standards.md +13 -0
  29. package/framework/rules/neon-standards.md +1 -0
  30. package/framework/skills/level-0-meta/mcp-registry.json +34 -1
  31. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +3 -3
  32. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
  33. package/framework/skills/level-0-meta/morph-init/SKILL.md +4 -4
  34. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
  35. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +11 -1
  36. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +12 -1
  37. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
  38. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
  39. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +119 -11
  40. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  41. package/framework/standards/STANDARDS.json +13 -9
  42. package/framework/standards/ai-agents/production.md +5 -2
  43. package/framework/standards/ai-agents/setup.md +13 -14
  44. package/framework/standards/ai-agents/workflows.md +8 -6
  45. package/framework/standards/backend/database/postgresql/database.md +5 -5
  46. package/framework/standards/backend/database/vector-search-rag.md +1 -1
  47. package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
  48. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +364 -0
  49. package/framework/standards/frontend/nextjs/nextjs-patterns.md +9 -9
  50. package/framework/templates/README.md +2 -2
  51. package/framework/templates/REGISTRY.json +5 -5
  52. package/framework/templates/code/dotnet/jobs/agent.cs +0 -1
  53. package/framework/templates/code/sql/postgresql-migration.sql +10 -10
  54. package/framework/templates/code/sql/postgresql-migration.template.sql +12 -14
  55. package/framework/templates/code/sql/postgresql-rls-policy.sql +6 -9
  56. package/framework/templates/feature/decisions.md +8 -7
  57. package/framework/templates/integrations/neon-auth-config.cs +133 -0
  58. package/framework/workflows/docs/full-morph.md +1 -1
  59. package/package.json +1 -1
  60. package/src/commands/mcp/mcp-setup.js +21 -0
  61. package/src/commands/project/init.js +11 -0
  62. package/src/lib/contracts/sprint-contract-generator.js +137 -0
  63. package/src/lib/detectors/index.js +2 -2
  64. package/src/lib/generators/architecture-scanner.js +2 -3
  65. package/src/lib/installers/mcp-installer.js +25 -0
  66. package/src/utils/hooks-installer.js +12 -1
  67. package/framework/agents/integrations/clerk-auth.md +0 -109
  68. package/framework/rules/clerk-standards.md +0 -13
  69. package/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -292
  70. package/framework/templates/integrations/clerk-config.cs +0 -217
@@ -0,0 +1,364 @@
1
+ # Neon Auth (Better Auth) Integration Standard
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** neon auth, better auth, authentication, nextjs, user management, session, JWT
6
+ > **Load When:** neon auth or authentication keywords detected
7
+
8
+ Managed authentication powered by Better Auth, with all auth data stored directly in your Neon database (`neon_auth` schema). No external auth providers, no webhooks, no sync delays.
9
+
10
+ ---
11
+
12
+ ## Overview
13
+
14
+ Neon Auth provides:
15
+ - Pre-built UI components (AuthView, UserButton, SignedIn)
16
+ - Social auth (Google, GitHub, etc.)
17
+ - Session management with HTTP-only cookies
18
+ - JWT tokens for Row-Level Security
19
+ - Branch-isolated auth data (perfect for preview environments)
20
+ - Database as single source of truth
21
+
22
+ **Primary Use Case:** Next.js 15 + App Router + Neon PostgreSQL
23
+
24
+ ---
25
+
26
+ ## Core Principles
27
+
28
+ 1. **Database-First**: Auth data lives in `neon_auth` schema — no external service to sync
29
+ 2. **Branch Isolation**: Each Neon branch gets isolated users, sessions, and auth config
30
+ 3. **Server-First**: Use server-side auth checks with `auth.getSession()`
31
+ 4. **JWT for RLS**: Tokens contain `sub` (user ID), `email`, `role: "authenticated"` for database policies
32
+
33
+ ---
34
+
35
+ ## Installation & Setup
36
+
37
+ ### Install Packages
38
+
39
+ ```bash
40
+ npm install @neondatabase/auth@latest
41
+ ```
42
+
43
+ ### Environment Variables
44
+
45
+ ```bash
46
+ # .env.local
47
+ NEON_AUTH_BASE_URL=https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth
48
+ NEON_AUTH_COOKIE_SECRET=your-secret-at-least-32-characters-long
49
+ ```
50
+
51
+ Generate cookie secret:
52
+ ```bash
53
+ openssl rand -base64 32
54
+ ```
55
+
56
+ ### SDK Initialization
57
+
58
+ ```typescript
59
+ // lib/auth/client.ts
60
+ 'use client';
61
+ import { createAuthClient } from '@neondatabase/auth/next';
62
+ export const authClient = createAuthClient();
63
+
64
+ // lib/auth/server.ts
65
+ import { createNeonAuth } from '@neondatabase/auth/next/server';
66
+ export const auth = createNeonAuth({
67
+ baseUrl: process.env.NEON_AUTH_BASE_URL!,
68
+ cookies: {
69
+ secret: process.env.NEON_AUTH_COOKIE_SECRET!,
70
+ },
71
+ });
72
+ ```
73
+
74
+ ### Root Layout Provider
75
+
76
+ ```tsx
77
+ // app/providers.tsx
78
+ 'use client';
79
+ import { NeonAuthUIProvider } from '@neondatabase/auth/react';
80
+ import '@neondatabase/auth/ui/css';
81
+ import Link from 'next/link';
82
+ import { useRouter } from 'next/navigation';
83
+ import { authClient } from '@/lib/auth/client';
84
+
85
+ export function Providers({ children }: { children: React.ReactNode }) {
86
+ const router = useRouter();
87
+ return (
88
+ <NeonAuthUIProvider
89
+ authClient={authClient}
90
+ navigate={router.push}
91
+ replace={router.replace}
92
+ onSessionChange={router.refresh}
93
+ Link={Link}
94
+ >
95
+ {children}
96
+ </NeonAuthUIProvider>
97
+ );
98
+ }
99
+ ```
100
+
101
+ ### Auth API Route
102
+
103
+ ```typescript
104
+ // app/api/auth/[...path]/route.ts
105
+ import { auth } from '@/lib/auth/server';
106
+
107
+ export const { GET, POST } = auth.handler();
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Common Patterns
113
+
114
+ ### Sign In / Sign Up Pages
115
+
116
+ ```tsx
117
+ // app/auth/sign-in/page.tsx
118
+ import { AuthView } from '@neondatabase/auth/react';
119
+
120
+ export default function SignInPage() {
121
+ return (
122
+ <div className="flex items-center justify-center min-h-screen">
123
+ <AuthView pathname="sign-in" />
124
+ </div>
125
+ );
126
+ }
127
+
128
+ // app/auth/sign-up/page.tsx
129
+ import { AuthView } from '@neondatabase/auth/react';
130
+
131
+ export default function SignUpPage() {
132
+ return (
133
+ <div className="flex items-center justify-center min-h-screen">
134
+ <AuthView pathname="sign-up" />
135
+ </div>
136
+ );
137
+ }
138
+ ```
139
+
140
+ ### Server-Side Auth Check
141
+
142
+ ```typescript
143
+ // app/dashboard/page.tsx
144
+ import { auth } from '@/lib/auth/server';
145
+ import { redirect } from 'next/navigation';
146
+
147
+ export const dynamic = 'force-dynamic';
148
+
149
+ export default async function DashboardPage() {
150
+ const { data: session } = await auth.getSession();
151
+
152
+ if (!session?.user) {
153
+ redirect('/auth/sign-in');
154
+ }
155
+
156
+ return <div>Dashboard for {session.user.name || session.user.email}</div>;
157
+ }
158
+ ```
159
+
160
+ ### Client Component with User Data
161
+
162
+ ```tsx
163
+ // components/UserProfile.tsx
164
+ 'use client';
165
+ import { useSession } from '@/lib/auth/client';
166
+
167
+ export function UserProfile() {
168
+ const { data } = useSession();
169
+ const user = data?.user;
170
+
171
+ if (!user) return <div>Not signed in</div>;
172
+
173
+ return (
174
+ <div>
175
+ <p>Welcome, {user.name || user.email}!</p>
176
+ </div>
177
+ );
178
+ }
179
+ ```
180
+
181
+ ### Route Protection (Middleware)
182
+
183
+ ```typescript
184
+ // middleware.ts
185
+ import { auth } from '@/lib/auth/server';
186
+
187
+ export default auth.middleware({
188
+ loginUrl: '/auth/sign-in',
189
+ });
190
+
191
+ export const config = {
192
+ matcher: [
193
+ '/dashboard/:path*',
194
+ '/settings/:path*',
195
+ '/((?!_next/static|_next/image|favicon.ico|auth).*)',
196
+ ],
197
+ };
198
+ ```
199
+
200
+ ### Component-Level Protection
201
+
202
+ ```tsx
203
+ import { SignedIn, RedirectToSignIn } from '@neondatabase/auth/react';
204
+
205
+ export default function ProtectedPage() {
206
+ return (
207
+ <>
208
+ <SignedIn>
209
+ <div>Protected content</div>
210
+ </SignedIn>
211
+ <RedirectToSignIn />
212
+ </>
213
+ );
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## OAuth (Social Login)
220
+
221
+ ```typescript
222
+ // Trigger OAuth sign-in
223
+ await authClient.signIn.social({
224
+ provider: 'google',
225
+ callbackURL: 'http://localhost:3000/auth/callback',
226
+ });
227
+ ```
228
+
229
+ Configure OAuth providers in the Neon Console under Auth > Configuration.
230
+
231
+ ---
232
+
233
+ ## API Route Protection
234
+
235
+ ```typescript
236
+ // app/api/protected/route.ts
237
+ import { auth } from '@/lib/auth/server';
238
+
239
+ export const dynamic = 'force-dynamic';
240
+
241
+ export async function GET() {
242
+ const { data: session } = await auth.getSession();
243
+
244
+ if (!session?.user) {
245
+ return new Response('Unauthorized', { status: 401 });
246
+ }
247
+
248
+ return Response.json({ userId: session.user.id });
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ ## .NET Backend Integration
255
+
256
+ Neon Auth issues standard JWTs. The .NET API validates these tokens directly — no external auth SDK needed.
257
+
258
+ ### JWT Bearer Configuration
259
+
260
+ ```csharp
261
+ // Program.cs
262
+ builder.Services.AddAuthentication("NeonAuth")
263
+ .AddJwtBearer("NeonAuth", options =>
264
+ {
265
+ options.Authority = builder.Configuration["NeonAuth:BaseUrl"];
266
+ options.TokenValidationParameters = new()
267
+ {
268
+ ValidateIssuer = true,
269
+ ValidateAudience = false,
270
+ ValidateLifetime = true,
271
+ ValidateIssuerSigningKey = true,
272
+ NameClaimType = "sub"
273
+ };
274
+ });
275
+
276
+ builder.Services.AddAuthorization();
277
+ ```
278
+
279
+ ### Protected Endpoints
280
+
281
+ ```csharp
282
+ app.MapGet("/api/profile", (ClaimsPrincipal user) =>
283
+ {
284
+ var userId = user.FindFirstValue("sub");
285
+ if (userId is null) return Results.Unauthorized();
286
+ return Results.Ok(new { UserId = userId });
287
+ }).RequireAuthorization();
288
+ ```
289
+
290
+ ### appsettings.json
291
+
292
+ ```json
293
+ {
294
+ "NeonAuth": {
295
+ "BaseUrl": "https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth"
296
+ }
297
+ }
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Row-Level Security Integration
303
+
304
+ Neon Auth JWT tokens are automatically validated by the Data API. The `auth.user_id()` function (via `pg_session_jwt` extension) exposes the authenticated user ID from the JWT `sub` claim.
305
+
306
+ ```sql
307
+ -- Enable RLS
308
+ ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
309
+
310
+ -- Users can only access their own rows
311
+ CREATE POLICY "Users can view own documents"
312
+ ON public.documents FOR SELECT TO authenticated
313
+ USING (user_id = auth.user_id());
314
+
315
+ CREATE POLICY "Users can insert own documents"
316
+ ON public.documents FOR INSERT TO authenticated
317
+ WITH CHECK (user_id = auth.user_id());
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Database Schema
323
+
324
+ Neon Auth automatically manages the `neon_auth` schema with these tables:
325
+ - `neon_auth.user` — User profiles
326
+ - `neon_auth.session` — Active sessions
327
+ - `neon_auth.account` — Auth credentials (password hashes, OAuth tokens)
328
+
329
+ You can query these tables directly for user data — no webhook sync needed:
330
+ ```sql
331
+ SELECT id, email, name FROM neon_auth.user WHERE id = auth.user_id();
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Branch Isolation
337
+
338
+ Each Neon branch gets its own isolated auth data. This means:
339
+ - Preview branches have separate users and sessions
340
+ - Feature branches don't share auth state with production
341
+ - Perfect for PR preview environments
342
+
343
+ ---
344
+
345
+ ## Best Practices
346
+
347
+ 1. **Always use `force-dynamic`** on pages/routes that call `auth.getSession()`
348
+ 2. **Cookie secret must be 32+ characters** — generate with `openssl rand -base64 32`
349
+ 3. **Use `auth.user_id()` for RLS** — never extract JWT claims manually in SQL
350
+ 4. **Configure OAuth in Neon Console** — not in code
351
+ 5. **Use `SignedIn` / `RedirectToSignIn`** for client-side protection
352
+ 6. **Use middleware** for broad route protection patterns
353
+
354
+ ---
355
+
356
+ ## References
357
+
358
+ - [Neon Auth Documentation](https://neon.com/docs/auth)
359
+ - [Neon Auth Authentication Flow](https://neon.com/docs/auth/authentication-flow)
360
+ - [Migration from Legacy Auth](https://neon.com/docs/auth/migrate/from-legacy-auth)
361
+
362
+ ---
363
+
364
+ *MORPH-SPEC by Polymorphism Tech*
@@ -65,10 +65,12 @@ export default function Error({ error, reset }: { error: Error; reset: () => voi
65
65
 
66
66
  ```ts
67
67
  // app/api/documents/route.ts — BFF pattern (delegates to .NET API)
68
- import { auth } from "@clerk/nextjs/server";
68
+ import { auth } from "@/lib/auth/server";
69
69
  import { NextResponse } from "next/server";
70
70
  import { z } from "zod";
71
71
 
72
+ export const dynamic = "force-dynamic";
73
+
72
74
  const API_URL = process.env.API_URL!; // .NET backend
73
75
 
74
76
  const CreateSchema = z.object({
@@ -77,27 +79,25 @@ const CreateSchema = z.object({
77
79
  });
78
80
 
79
81
  export async function GET() {
80
- const { userId, getToken } = await auth();
81
- if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
82
+ const { data: session } = await auth.getSession();
83
+ if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
82
84
 
83
- const token = await getToken();
84
85
  const res = await fetch(`${API_URL}/api/documents`, {
85
- headers: { Authorization: `Bearer ${token}` },
86
+ headers: { Authorization: `Bearer ${session.session.access_token}` },
86
87
  });
87
88
  return NextResponse.json(await res.json());
88
89
  }
89
90
 
90
91
  export async function POST(request: Request) {
91
- const { userId, getToken } = await auth();
92
- if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
92
+ const { data: session } = await auth.getSession();
93
+ if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
93
94
 
94
95
  const parsed = CreateSchema.safeParse(await request.json());
95
96
  if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
96
97
 
97
- const token = await getToken();
98
98
  const res = await fetch(`${API_URL}/api/documents`, {
99
99
  method: "POST",
100
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
100
+ headers: { Authorization: `Bearer ${session.session.access_token}`, "Content-Type": "application/json" },
101
101
  body: JSON.stringify(parsed.data),
102
102
  });
103
103
  return NextResponse.json(await res.json(), { status: res.status });
@@ -77,7 +77,7 @@ framework/templates/
77
77
  ├── integrations/ # Platform-agnostic integrations
78
78
  │ ├── asaas-client.cs # Asaas financial API
79
79
  │ ├── asaas-webhook.cs # Asaas webhooks
80
- │ └── clerk-config.cs # Clerk auth
80
+ │ └── neon-auth-config.cs # Neon Auth (Better Auth)
81
81
  │
82
82
  ├── ui/ # Design systems
83
83
  │ └── design-system.css # CSS tokens
@@ -107,7 +107,7 @@ framework/templates/
107
107
  **Future-proof:** When we create `vue-firebase` stack, we can reuse:
108
108
  - `code/typescript/` templates
109
109
  - `infrastructure/docker/` templates
110
- - `integrations/` templates (Clerk, etc.)
110
+ - `integrations/` templates (Neon Auth, etc.)
111
111
 
112
112
  **Only create NEW templates for Vue-specific code.**
113
113
 
@@ -597,15 +597,15 @@
597
597
  ]
598
598
  },
599
599
  {
600
- "id": "integration-clerk",
601
- "name": "Clerk Authentication",
602
- "path": "integrations/clerk-config.cs",
600
+ "id": "integration-neon-auth",
601
+ "name": "Neon Auth Configuration",
602
+ "path": "integrations/neon-auth-config.cs",
603
603
  "location": "framework",
604
604
  "category": "integration",
605
605
  "phase": "implement",
606
606
  "required": false,
607
- "description": "Clerk authentication configuration",
608
- "outputName": "ClerkConfig.cs",
607
+ "description": "Neon Auth (Better Auth) authentication configuration for .NET",
608
+ "outputName": "NeonAuthConfig.cs",
609
609
  "placeholders": [
610
610
  "NAMESPACE"
611
611
  ],
@@ -5,7 +5,6 @@
5
5
 
6
6
  using System.ComponentModel;
7
7
  using System.Text.Json;
8
- using Microsoft.Agents.AI;
9
8
  using Microsoft.Extensions.AI;
10
9
  using Microsoft.Extensions.Logging;
11
10
 
@@ -65,27 +65,27 @@ create trigger set_{{TABLE_NAME}}_updated_at
65
65
  -- Enable RLS
66
66
  alter table public.{{TABLE_NAME}} enable row level security;
67
67
 
68
- -- Users can read their own rows (Clerk JWT)
68
+ -- Users can read their own rows (Neon Auth JWT via pg_session_jwt)
69
69
  create policy "Users can read own {{TABLE_NAME}}"
70
70
  on public.{{TABLE_NAME}}
71
- for select
72
- using (current_setting('request.jwt.claims')::json->>'sub' = user_id);
71
+ for select to authenticated
72
+ using (auth.user_id() = user_id);
73
73
 
74
74
  -- Users can insert rows for themselves
75
75
  create policy "Users can insert own {{TABLE_NAME}}"
76
76
  on public.{{TABLE_NAME}}
77
- for insert
78
- with check (current_setting('request.jwt.claims')::json->>'sub' = user_id);
77
+ for insert to authenticated
78
+ with check (auth.user_id() = user_id);
79
79
 
80
80
  -- Users can update their own rows
81
81
  create policy "Users can update own {{TABLE_NAME}}"
82
82
  on public.{{TABLE_NAME}}
83
- for update
84
- using (current_setting('request.jwt.claims')::json->>'sub' = user_id)
85
- with check (current_setting('request.jwt.claims')::json->>'sub' = user_id);
83
+ for update to authenticated
84
+ using (auth.user_id() = user_id)
85
+ with check (auth.user_id() = user_id);
86
86
 
87
87
  -- Users can delete their own rows
88
88
  create policy "Users can delete own {{TABLE_NAME}}"
89
89
  on public.{{TABLE_NAME}}
90
- for delete
91
- using (current_setting('request.jwt.claims')::json->>'sub' = user_id);
90
+ for delete to authenticated
91
+ using (auth.user_id() = user_id);
@@ -70,28 +70,26 @@ create policy "{{TABLE_NAME}}_select_policy"
70
70
  on public.{{TABLE_NAME}} for select
71
71
  using (true);
72
72
 
73
- -- Policy: Users can only insert their own records (Clerk JWT)
73
+ -- Policy: Users can only insert their own records (Neon Auth + pg_session_jwt)
74
74
  -- create policy "{{TABLE_NAME}}_insert_policy"
75
- -- on public.{{TABLE_NAME}} for insert
76
- -- with check (current_setting('request.jwt.claims')::json->>'sub' = user_id);
75
+ -- on public.{{TABLE_NAME}} for insert to authenticated
76
+ -- with check (auth.user_id() = user_id);
77
77
 
78
- -- Policy: Users can only update their own records (Clerk JWT)
78
+ -- Policy: Users can only update their own records (Neon Auth + pg_session_jwt)
79
79
  -- create policy "{{TABLE_NAME}}_update_policy"
80
- -- on public.{{TABLE_NAME}} for update
81
- -- using (current_setting('request.jwt.claims')::json->>'sub' = user_id)
82
- -- with check (current_setting('request.jwt.claims')::json->>'sub' = user_id);
80
+ -- on public.{{TABLE_NAME}} for update to authenticated
81
+ -- using (auth.user_id() = user_id)
82
+ -- with check (auth.user_id() = user_id);
83
83
 
84
- -- Policy: Users can only delete their own records (Clerk JWT)
84
+ -- Policy: Users can only delete their own records (Neon Auth + pg_session_jwt)
85
85
  -- create policy "{{TABLE_NAME}}_delete_policy"
86
- -- on public.{{TABLE_NAME}} for delete
87
- -- using (current_setting('request.jwt.claims')::json->>'sub' = user_id);
86
+ -- on public.{{TABLE_NAME}} for delete to authenticated
87
+ -- using (auth.user_id() = user_id);
88
88
 
89
- -- Policy: Admins have full access (Clerk JWT)
89
+ -- Policy: Admins have full access (Neon Auth + pg_session_jwt)
90
90
  -- create policy "{{TABLE_NAME}}_admin_policy"
91
91
  -- on public.{{TABLE_NAME}} for all
92
- -- using (
93
- -- current_setting('request.jwt.claims')::json->>'role' = 'admin'
94
- -- );
92
+ -- using (auth.jwt()->>'role' = 'admin');
95
93
 
96
94
  -- ========================================
97
95
  -- Seed Data (Optional)
@@ -40,18 +40,15 @@ create policy "{{POLICY_NAME}}_delete"
40
40
  using ({{USING_EXPRESSION}});
41
41
 
42
42
  -- ============================================================
43
- -- Common USING / WITH CHECK expressions:
43
+ -- Common USING / WITH CHECK expressions (Neon Auth + pg_session_jwt):
44
44
  --
45
- -- Owner-based (Clerk JWT):
46
- -- current_setting('request.jwt.claims')::json->>'sub' = user_id
45
+ -- Owner-based:
46
+ -- auth.user_id() = user_id
47
47
  --
48
- -- Role-based (Clerk JWT):
49
- -- current_setting('request.jwt.claims')::json->>'role' = 'admin'
50
- --
51
- -- Organization-based (Clerk JWT):
52
- -- org_id = (current_setting('request.jwt.claims')::json->>'org_id')
48
+ -- Role-based (extract from JWT claims):
49
+ -- auth.jwt()->>'role' = 'admin'
53
50
  --
54
51
  -- Public read, owner write:
55
52
  -- SELECT using (true)
56
- -- INSERT/UPDATE/DELETE using (current_setting('request.jwt.claims')::json->>'sub' = user_id)
53
+ -- INSERT/UPDATE/DELETE using (auth.user_id() = user_id)
57
54
  -- ============================================================
@@ -80,19 +80,20 @@ Need authentication for both Next.js frontend and .NET API.
80
80
 
81
81
  ### Decision
82
82
 
83
- Use Clerk for authentication on frontend. Validate JWT tokens in .NET API middleware.
83
+ Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
84
84
 
85
85
  ### Consequences
86
86
 
87
87
  **Pros:**
88
- - Single auth provider for both layers
89
- - RLS policies work with Clerk JWT claims via current_setting('request.jwt.claims')
90
- - Built-in social login, magic links, email/password
91
- - No separate auth service to maintain
88
+ - Auth data in same database — no external provider, no sync delays
89
+ - RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
90
+ - Built-in social login, email/password via @neondatabase/auth SDK
91
+ - Branch isolation — each Neon branch gets isolated auth data
92
+ - No webhooks needed — query neon_auth schema directly
92
93
 
93
94
  **Cons:**
94
- - Clerk JWT must be forwarded to database connections for RLS
95
- - JWT secret must be shared with .NET API
95
+ - Neon Auth is currently in Beta
96
+ - JWT must be forwarded to .NET API for backend auth
96
97
  {{/if}}
97
98
 
98
99
  ---