mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
* chore(ai-workflow): track repo-managed review tooling * fix(ai-workflow): remove repo-specific path assumptions Make shared workflow hooks and APK testing guidance resolve paths from the repo and contributor environment so the tooling works for all contributors, not just one machine.
97 lines
2.6 KiB
Markdown
97 lines
2.6 KiB
Markdown
---
|
|
title: Authenticate Server Actions Like API Routes
|
|
impact: CRITICAL
|
|
impactDescription: prevents unauthorized access to server mutations
|
|
tags: server, server-actions, authentication, security, authorization
|
|
---
|
|
|
|
## Authenticate Server Actions Like API Routes
|
|
|
|
**Impact: CRITICAL (prevents unauthorized access to server mutations)**
|
|
|
|
Server Actions (functions with `"use server"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.
|
|
|
|
Next.js documentation explicitly states: "Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation."
|
|
|
|
**Incorrect (no authentication check):**
|
|
|
|
```typescript
|
|
'use server'
|
|
|
|
export async function deleteUser(userId: string) {
|
|
// Anyone can call this! No auth check
|
|
await db.user.delete({ where: { id: userId } })
|
|
return { success: true }
|
|
}
|
|
```
|
|
|
|
**Correct (authentication inside the action):**
|
|
|
|
```typescript
|
|
'use server'
|
|
|
|
import { verifySession } from '@/lib/auth'
|
|
import { unauthorized } from '@/lib/errors'
|
|
|
|
export async function deleteUser(userId: string) {
|
|
// Always check auth inside the action
|
|
const session = await verifySession()
|
|
|
|
if (!session) {
|
|
throw unauthorized('Must be logged in')
|
|
}
|
|
|
|
// Check authorization too
|
|
if (session.user.role !== 'admin' && session.user.id !== userId) {
|
|
throw unauthorized('Cannot delete other users')
|
|
}
|
|
|
|
await db.user.delete({ where: { id: userId } })
|
|
return { success: true }
|
|
}
|
|
```
|
|
|
|
**With input validation:**
|
|
|
|
```typescript
|
|
'use server'
|
|
|
|
import { verifySession } from '@/lib/auth'
|
|
import { z } from 'zod'
|
|
|
|
const updateProfileSchema = z.object({
|
|
userId: z.string().uuid(),
|
|
name: z.string().min(1).max(100),
|
|
email: z.string().email()
|
|
})
|
|
|
|
export async function updateProfile(data: unknown) {
|
|
// Validate input first
|
|
const validated = updateProfileSchema.parse(data)
|
|
|
|
// Then authenticate
|
|
const session = await verifySession()
|
|
if (!session) {
|
|
throw new Error('Unauthorized')
|
|
}
|
|
|
|
// Then authorize
|
|
if (session.user.id !== validated.userId) {
|
|
throw new Error('Can only update own profile')
|
|
}
|
|
|
|
// Finally perform the mutation
|
|
await db.user.update({
|
|
where: { id: validated.userId },
|
|
data: {
|
|
name: validated.name,
|
|
email: validated.email
|
|
}
|
|
})
|
|
|
|
return { success: true }
|
|
}
|
|
```
|
|
|
|
Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)
|