@polymorphism-tech/morph-spec 4.17.0 → 4.18.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.
- package/CLAUDE.md +36 -0
- package/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents/backend/neon-expert.md +9 -27
- package/framework/agents/integrations/neon-auth.md +151 -0
- package/framework/agents.json +21 -13
- package/framework/rules/neon-auth-standards.md +13 -0
- package/framework/rules/neon-standards.md +1 -0
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +3 -3
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +4 -4
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +13 -9
- package/framework/standards/backend/database/postgresql/database.md +5 -5
- package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +364 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +9 -9
- package/framework/templates/README.md +2 -2
- package/framework/templates/REGISTRY.json +5 -5
- package/framework/templates/code/sql/postgresql-migration.sql +10 -10
- package/framework/templates/code/sql/postgresql-migration.template.sql +12 -14
- package/framework/templates/code/sql/postgresql-rls-policy.sql +6 -9
- package/framework/templates/feature/decisions.md +8 -7
- package/framework/templates/integrations/neon-auth-config.cs +133 -0
- package/framework/workflows/docs/full-morph.md +1 -1
- package/package.json +1 -1
- package/src/lib/detectors/index.js +2 -2
- package/src/lib/generators/architecture-scanner.js +2 -3
- package/framework/agents/integrations/clerk-auth.md +0 -109
- package/framework/rules/clerk-standards.md +0 -13
- package/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -292
- package/framework/templates/integrations/clerk-config.cs +0 -217
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// ==============================================================================
|
|
2
|
+
// MORPH-SPEC - Neon Auth Configuration Template
|
|
3
|
+
// Configuracao de autenticacao com Neon Auth (Better Auth) para .NET
|
|
4
|
+
// ==============================================================================
|
|
5
|
+
|
|
6
|
+
using Microsoft.AspNetCore.Authentication;
|
|
7
|
+
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
8
|
+
using Microsoft.IdentityModel.Tokens;
|
|
9
|
+
using System.Security.Claims;
|
|
10
|
+
|
|
11
|
+
namespace {{Namespace}}.Infrastructure.Auth;
|
|
12
|
+
|
|
13
|
+
// ==============================================================================
|
|
14
|
+
// OPTIONS
|
|
15
|
+
// ==============================================================================
|
|
16
|
+
|
|
17
|
+
public class NeonAuthOptions
|
|
18
|
+
{
|
|
19
|
+
public const string SectionName = "NeonAuth";
|
|
20
|
+
|
|
21
|
+
/// <summary>
|
|
22
|
+
/// Neon Auth Base URL (e.g., https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth)
|
|
23
|
+
/// </summary>
|
|
24
|
+
public string BaseUrl { get; set; } = string.Empty;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ==============================================================================
|
|
28
|
+
// SERVICE EXTENSIONS
|
|
29
|
+
// ==============================================================================
|
|
30
|
+
|
|
31
|
+
public static class NeonAuthServiceExtensions
|
|
32
|
+
{
|
|
33
|
+
/// <summary>
|
|
34
|
+
/// Adds Neon Auth JWT Bearer authentication to the project.
|
|
35
|
+
/// Validates JWTs issued by Neon Auth service.
|
|
36
|
+
/// </summary>
|
|
37
|
+
public static IServiceCollection AddNeonAuthentication(
|
|
38
|
+
this IServiceCollection services,
|
|
39
|
+
IConfiguration configuration)
|
|
40
|
+
{
|
|
41
|
+
services.Configure<NeonAuthOptions>(configuration.GetSection(NeonAuthOptions.SectionName));
|
|
42
|
+
|
|
43
|
+
var neonAuthOptions = configuration.GetSection(NeonAuthOptions.SectionName).Get<NeonAuthOptions>()!;
|
|
44
|
+
|
|
45
|
+
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
46
|
+
.AddJwtBearer(options =>
|
|
47
|
+
{
|
|
48
|
+
options.Authority = neonAuthOptions.BaseUrl;
|
|
49
|
+
options.TokenValidationParameters = new TokenValidationParameters
|
|
50
|
+
{
|
|
51
|
+
ValidateIssuer = true,
|
|
52
|
+
ValidateAudience = false,
|
|
53
|
+
ValidateLifetime = true,
|
|
54
|
+
ValidateIssuerSigningKey = true,
|
|
55
|
+
NameClaimType = "sub"
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
services.AddAuthorization();
|
|
60
|
+
|
|
61
|
+
return services;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ==============================================================================
|
|
66
|
+
// USER CONTEXT SERVICE
|
|
67
|
+
// ==============================================================================
|
|
68
|
+
|
|
69
|
+
public interface ICurrentUserService
|
|
70
|
+
{
|
|
71
|
+
string? UserId { get; }
|
|
72
|
+
string? Email { get; }
|
|
73
|
+
bool IsAuthenticated { get; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public class CurrentUserService(IHttpContextAccessor httpContextAccessor) : ICurrentUserService
|
|
77
|
+
{
|
|
78
|
+
public string? UserId =>
|
|
79
|
+
httpContextAccessor.HttpContext?.User.FindFirstValue("sub");
|
|
80
|
+
|
|
81
|
+
public string? Email =>
|
|
82
|
+
httpContextAccessor.HttpContext?.User.FindFirstValue("email");
|
|
83
|
+
|
|
84
|
+
public bool IsAuthenticated =>
|
|
85
|
+
httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ==============================================================================
|
|
89
|
+
// MIDDLEWARE PIPELINE
|
|
90
|
+
// ==============================================================================
|
|
91
|
+
|
|
92
|
+
public static class NeonAuthMiddlewareExtensions
|
|
93
|
+
{
|
|
94
|
+
public static IApplicationBuilder UseNeonAuthentication(this IApplicationBuilder app)
|
|
95
|
+
{
|
|
96
|
+
app.UseAuthentication();
|
|
97
|
+
app.UseAuthorization();
|
|
98
|
+
return app;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ==============================================================================
|
|
103
|
+
// APPSETTINGS EXAMPLE
|
|
104
|
+
// ==============================================================================
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
{
|
|
108
|
+
"NeonAuth": {
|
|
109
|
+
"BaseUrl": "https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
// ==============================================================================
|
|
115
|
+
// PROGRAM.CS EXAMPLE
|
|
116
|
+
// ==============================================================================
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
120
|
+
|
|
121
|
+
// Add Neon Auth authentication
|
|
122
|
+
builder.Services.AddNeonAuthentication(builder.Configuration);
|
|
123
|
+
builder.Services.AddHttpContextAccessor();
|
|
124
|
+
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
|
125
|
+
|
|
126
|
+
var app = builder.Build();
|
|
127
|
+
|
|
128
|
+
app.UseNeonAuthentication();
|
|
129
|
+
|
|
130
|
+
app.MapControllers().RequireAuthorization();
|
|
131
|
+
|
|
132
|
+
app.Run();
|
|
133
|
+
*/
|
|
@@ -63,7 +63,7 @@ proposal.md, design-system.md, mockups.md, components.md, flows.md, spec.md, con
|
|
|
63
63
|
|
|
64
64
|
## Exemplos
|
|
65
65
|
- "Sistema de relatórios com jobs agendados Hangfire"
|
|
66
|
-
- "Autenticação completa com
|
|
66
|
+
- "Autenticação completa com Neon Auth (login, signup, OAuth)"
|
|
67
67
|
- "Dashboard com gráficos, tabelas e filtros complexos"
|
|
68
68
|
- "RAG pipeline com AI Search e embeddings"
|
|
69
69
|
- "Infraestrutura completa com Docker/Easypanel"
|
package/package.json
CHANGED
|
@@ -74,8 +74,8 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
74
74
|
if (depNames.includes('@neondatabase/serverless') || depNames.includes('postgres') || depNames.includes('pg')) {
|
|
75
75
|
structure.integrations.push('neon');
|
|
76
76
|
}
|
|
77
|
-
if (depNames.includes('@
|
|
78
|
-
structure.integrations.push('
|
|
77
|
+
if (depNames.includes('@neondatabase/auth')) {
|
|
78
|
+
structure.integrations.push('neon-auth');
|
|
79
79
|
}
|
|
80
80
|
if (depNames.includes('stripe')) {
|
|
81
81
|
structure.integrations.push('stripe');
|
|
@@ -223,7 +223,7 @@ export async function scanIntegrations(projectPath) {
|
|
|
223
223
|
);
|
|
224
224
|
|
|
225
225
|
const knownServices = {
|
|
226
|
-
'
|
|
226
|
+
'NeonAuth': 'auth',
|
|
227
227
|
'AsaaS': 'payment',
|
|
228
228
|
'Resend': 'email',
|
|
229
229
|
'Hangfire': 'background-jobs',
|
|
@@ -266,8 +266,7 @@ export async function scanIntegrations(projectPath) {
|
|
|
266
266
|
|
|
267
267
|
// Scan .env files for known prefixes
|
|
268
268
|
const envPrefixes = {
|
|
269
|
-
'
|
|
270
|
-
'CLERK_': 'Clerk',
|
|
269
|
+
'NEON_AUTH_': 'NeonAuth',
|
|
271
270
|
'DATABASE_URL': 'Neon',
|
|
272
271
|
'NEON_': 'Neon',
|
|
273
272
|
'RESEND_': 'Resend',
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: clerk-auth
|
|
3
|
-
description: Clerk Auth specialist for Clerk SDK integration, social login, JWT session management, and user management in Next.js and .NET projects. Use when integrating Clerk authentication, implementing social login flows, managing user sessions with Clerk JWT, or protecting routes with Clerk middleware.
|
|
4
|
-
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
-
stacks:
|
|
6
|
-
- clerk
|
|
7
|
-
color: teal
|
|
8
|
-
effort: medium
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Clerk Auth
|
|
12
|
-
|
|
13
|
-
Autenticação SaaS com Clerk para .NET e Next.js. SDK: `Clerk.Net.AspNetCore.Security` (.NET) / `@clerk/nextjs` (Next.js).
|
|
14
|
-
|
|
15
|
-
## Setup
|
|
16
|
-
|
|
17
|
-
```csharp
|
|
18
|
-
// appsettings.json
|
|
19
|
-
{ "Clerk": { "SecretKey": "${CLERK_SECRET_KEY}", "PublishableKey": "pk_test_xxx" } }
|
|
20
|
-
|
|
21
|
-
// Program.cs
|
|
22
|
-
builder.Services.AddClerk(builder.Configuration);
|
|
23
|
-
builder.Services.AddAuthentication(ClerkAuthenticationDefaults.AuthenticationScheme)
|
|
24
|
-
.AddClerk(o => { o.Authority = "https://clerk.{instance}.com"; o.ValidAudiences = ["your-app-id"]; });
|
|
25
|
-
builder.Services.AddAuthorization();
|
|
26
|
-
app.UseAuthentication();
|
|
27
|
-
app.UseAuthorization();
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Protected Endpoints
|
|
31
|
-
|
|
32
|
-
```csharp
|
|
33
|
-
// Minimal API
|
|
34
|
-
app.MapGet("/api/profile", async (ClaimsPrincipal user, IClerkClient clerk) =>
|
|
35
|
-
{
|
|
36
|
-
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
37
|
-
if (userId is null) return Results.Unauthorized();
|
|
38
|
-
var u = await clerk.Users.GetUserAsync(userId);
|
|
39
|
-
return Results.Ok(new { u.Id, Email = u.EmailAddresses.FirstOrDefault()?.EmailAddress });
|
|
40
|
-
}).RequireAuthorization();
|
|
41
|
-
|
|
42
|
-
// Controller: same pattern with [Authorize] + User.FindFirstValue()
|
|
43
|
-
// Role-based: [Authorize(Roles = "admin")]
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Next.js Integration
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
// middleware.ts
|
|
50
|
-
import { clerkMiddleware } from '@clerk/nextjs/server';
|
|
51
|
-
export default clerkMiddleware();
|
|
52
|
-
export const config = { matcher: ['/((?!.*\\..*|_next).*)', '/'] };
|
|
53
|
-
|
|
54
|
-
// Protected page
|
|
55
|
-
import { auth } from '@clerk/nextjs/server';
|
|
56
|
-
export default async function DashboardPage() {
|
|
57
|
-
const { userId } = await auth();
|
|
58
|
-
if (!userId) redirect('/sign-in');
|
|
59
|
-
return <Dashboard />;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Client component
|
|
63
|
-
'use client';
|
|
64
|
-
import { useUser } from '@clerk/nextjs';
|
|
65
|
-
const { user, isLoaded } = useUser();
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Client Operations (.NET)
|
|
69
|
-
|
|
70
|
-
```csharp
|
|
71
|
-
var user = await _clerk.Users.GetUserAsync(userId); // Get
|
|
72
|
-
var users = await _clerk.Users.GetUserListAsync(new() { Limit = 10 }); // List
|
|
73
|
-
await _clerk.Users.UpdateUserMetadataAsync(userId, new() // Update metadata
|
|
74
|
-
{ PublicMetadata = new Dictionary<string, object> { ["plan"] = "pro" } });
|
|
75
|
-
await _clerk.Users.DeleteUserAsync(userId); // Delete
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Webhooks
|
|
79
|
-
|
|
80
|
-
```csharp
|
|
81
|
-
[ApiController, Route("api/webhooks/clerk")]
|
|
82
|
-
public class ClerkWebhookController(IUserService users) : ControllerBase
|
|
83
|
-
{
|
|
84
|
-
[HttpPost]
|
|
85
|
-
public async Task<IActionResult> Handle([FromBody] ClerkWebhookPayload payload)
|
|
86
|
-
{
|
|
87
|
-
switch (payload.Type)
|
|
88
|
-
{
|
|
89
|
-
case "user.created": await users.SyncUserAsync(payload.Data); break;
|
|
90
|
-
case "user.updated": await users.UpdateUserAsync(payload.Data); break;
|
|
91
|
-
case "user.deleted": await users.DeleteUserAsync(payload.Data.Id); break;
|
|
92
|
-
}
|
|
93
|
-
return Ok();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Checklist
|
|
99
|
-
|
|
100
|
-
- [ ] Secret Key in environment variables (not hardcoded)
|
|
101
|
-
- [ ] Authentication scheme configured
|
|
102
|
-
- [ ] Authorization policies defined
|
|
103
|
-
- [ ] Webhook endpoint + signature validation
|
|
104
|
-
- [ ] Redirect after login configured
|
|
105
|
-
- [ ] Error handling for expired sessions
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
# Clerk Authentication Integration Standard
|
|
2
|
-
|
|
3
|
-
> **Scope:** universal
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** clerk, auth, authentication, nextjs, user management
|
|
6
|
-
> **Load When:** clerk authentication keywords detected
|
|
7
|
-
|
|
8
|
-
Modern authentication and user management for Next.js applications.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
Clerk provides:
|
|
15
|
-
- Pre-built UI components (SignIn, SignUp, UserProfile)
|
|
16
|
-
- Social auth (Google, GitHub, etc.)
|
|
17
|
-
- Multi-factor authentication
|
|
18
|
-
- Session management
|
|
19
|
-
- User metadata and organizations
|
|
20
|
-
|
|
21
|
-
**Primary Use Case:** Next.js 15 + App Router
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Core Principles
|
|
26
|
-
|
|
27
|
-
1. **Server-First**: Use server-side auth checks with `auth()` helper
|
|
28
|
-
2. **Route Protection**: Middleware-based protection for private routes
|
|
29
|
-
3. **Type Safety**: Use Clerk's TypeScript types
|
|
30
|
-
4. **Edge Compatible**: Works with Edge Runtime
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Installation & Setup
|
|
35
|
-
|
|
36
|
-
### Install Packages
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install @clerk/nextjs
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Environment Variables
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
# .env.local
|
|
46
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
|
|
47
|
-
CLERK_SECRET_KEY=sk_test_...
|
|
48
|
-
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
|
|
49
|
-
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
|
|
50
|
-
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
|
|
51
|
-
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Root Layout Wrapper
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// app/layout.tsx
|
|
58
|
-
import { ClerkProvider } from '@clerk/nextjs';
|
|
59
|
-
|
|
60
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
61
|
-
return (
|
|
62
|
-
<ClerkProvider>
|
|
63
|
-
<html lang="en">
|
|
64
|
-
<body>{children}</body>
|
|
65
|
-
</html>
|
|
66
|
-
</ClerkProvider>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Middleware Protection
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
// middleware.ts
|
|
75
|
-
import { authMiddleware } from '@clerk/nextjs';
|
|
76
|
-
|
|
77
|
-
export default authMiddleware({
|
|
78
|
-
publicRoutes: ['/', '/api/webhooks/clerk'],
|
|
79
|
-
ignoredRoutes: ['/api/public']
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export const config = {
|
|
83
|
-
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
|
|
84
|
-
};
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Common Patterns
|
|
90
|
-
|
|
91
|
-
### Sign In Page
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// app/sign-in/[[...sign-in]]/page.tsx
|
|
95
|
-
import { SignIn } from '@clerk/nextjs';
|
|
96
|
-
|
|
97
|
-
export default function SignInPage() {
|
|
98
|
-
return (
|
|
99
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
100
|
-
<SignIn />
|
|
101
|
-
</div>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Server-Side Auth Check
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// app/dashboard/page.tsx
|
|
110
|
-
import { auth } from '@clerk/nextjs';
|
|
111
|
-
import { redirect } from 'next/navigation';
|
|
112
|
-
|
|
113
|
-
export default async function DashboardPage() {
|
|
114
|
-
const { userId } = auth();
|
|
115
|
-
|
|
116
|
-
if (!userId) {
|
|
117
|
-
redirect('/sign-in');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return <div>Dashboard for user {userId}</div>;
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Client Component with User Data
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// components/UserProfile.tsx
|
|
128
|
-
'use client';
|
|
129
|
-
import { useUser } from '@clerk/nextjs';
|
|
130
|
-
|
|
131
|
-
export function UserProfile() {
|
|
132
|
-
const { user, isLoaded, isSignedIn } = useUser();
|
|
133
|
-
|
|
134
|
-
if (!isLoaded) return <div>Loading...</div>;
|
|
135
|
-
if (!isSignedIn) return <div>Not signed in</div>;
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div>
|
|
139
|
-
<p>Welcome, {user.firstName}!</p>
|
|
140
|
-
<p>Email: {user.primaryEmailAddress?.emailAddress}</p>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### API Route Protection
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// app/api/protected/route.ts
|
|
150
|
-
import { auth } from '@clerk/nextjs';
|
|
151
|
-
|
|
152
|
-
export async function GET() {
|
|
153
|
-
const { userId } = auth();
|
|
154
|
-
|
|
155
|
-
if (!userId) {
|
|
156
|
-
return new Response('Unauthorized', { status: 401 });
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Fetch user-specific data
|
|
160
|
-
const data = await fetchUserData(userId);
|
|
161
|
-
|
|
162
|
-
return Response.json(data);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Webhook Integration
|
|
169
|
-
|
|
170
|
-
### Setup Webhook Endpoint
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
// app/api/webhooks/clerk/route.ts
|
|
174
|
-
import { Webhook } from 'svix';
|
|
175
|
-
import { headers } from 'next/headers';
|
|
176
|
-
|
|
177
|
-
export async function POST(req: Request) {
|
|
178
|
-
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET!;
|
|
179
|
-
|
|
180
|
-
const headerPayload = headers();
|
|
181
|
-
const svix_id = headerPayload.get('svix-id');
|
|
182
|
-
const svix_timestamp = headerPayload.get('svix-timestamp');
|
|
183
|
-
const svix_signature = headerPayload.get('svix-signature');
|
|
184
|
-
|
|
185
|
-
if (!svix_id || !svix_timestamp || !svix_signature) {
|
|
186
|
-
return new Response('Missing svix headers', { status: 400 });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const payload = await req.json();
|
|
190
|
-
const body = JSON.stringify(payload);
|
|
191
|
-
|
|
192
|
-
const wh = new Webhook(WEBHOOK_SECRET);
|
|
193
|
-
|
|
194
|
-
let evt;
|
|
195
|
-
try {
|
|
196
|
-
evt = wh.verify(body, {
|
|
197
|
-
'svix-id': svix_id,
|
|
198
|
-
'svix-timestamp': svix_timestamp,
|
|
199
|
-
'svix-signature': svix_signature,
|
|
200
|
-
});
|
|
201
|
-
} catch (err) {
|
|
202
|
-
return new Response('Invalid signature', { status: 400 });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Handle events
|
|
206
|
-
switch (evt.type) {
|
|
207
|
-
case 'user.created':
|
|
208
|
-
await handleUserCreated(evt.data);
|
|
209
|
-
break;
|
|
210
|
-
case 'user.updated':
|
|
211
|
-
await handleUserUpdated(evt.data);
|
|
212
|
-
break;
|
|
213
|
-
case 'user.deleted':
|
|
214
|
-
await handleUserDeleted(evt.data);
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return new Response('OK');
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## Neon Integration
|
|
225
|
-
|
|
226
|
-
### Sync Users to Neon PostgreSQL (via EF Core / Npgsql)
|
|
227
|
-
|
|
228
|
-
```csharp
|
|
229
|
-
// Services/ClerkUserSyncService.cs
|
|
230
|
-
public sealed class ClerkUserSyncService(IDbContextFactory<AppDbContext> dbFactory)
|
|
231
|
-
{
|
|
232
|
-
public async Task HandleUserCreatedAsync(ClerkUserData userData, CancellationToken ct)
|
|
233
|
-
{
|
|
234
|
-
await using var db = await dbFactory.CreateDbContextAsync(ct);
|
|
235
|
-
|
|
236
|
-
db.Profiles.Add(new Profile
|
|
237
|
-
{
|
|
238
|
-
ClerkUserId = userData.Id,
|
|
239
|
-
Email = userData.EmailAddresses[0].EmailAddress,
|
|
240
|
-
FirstName = userData.FirstName,
|
|
241
|
-
LastName = userData.LastName,
|
|
242
|
-
AvatarUrl = userData.ImageUrl
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
await db.SaveChangesAsync(ct);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## Best Practices
|
|
253
|
-
|
|
254
|
-
### Custom Claims
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
// Add custom claims in Clerk Dashboard or via API
|
|
258
|
-
const { sessionClaims } = auth();
|
|
259
|
-
const role = sessionClaims?.metadata?.role as string;
|
|
260
|
-
|
|
261
|
-
if (role !== 'admin') {
|
|
262
|
-
return new Response('Forbidden', { status: 403 });
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Organizations
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
import { auth } from '@clerk/nextjs';
|
|
270
|
-
|
|
271
|
-
const { orgId, orgRole } = auth();
|
|
272
|
-
|
|
273
|
-
if (!orgId) {
|
|
274
|
-
redirect('/create-organization');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (orgRole !== 'admin') {
|
|
278
|
-
return <div>Admin access required</div>;
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## References
|
|
285
|
-
|
|
286
|
-
- [Clerk Next.js Documentation](https://clerk.com/docs/nextjs)
|
|
287
|
-
- [Clerk API Reference](https://clerk.com/docs/reference/backend-api)
|
|
288
|
-
- [Webhooks Guide](https://clerk.com/docs/integrations/webhooks)
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
*MORPH-SPEC by Polymorphism Tech*
|