返回 AiToEarn
server-auth-actions.md
1 ---
2 title: Authenticate Server Actions Like API Routes
3 impact: CRITICAL
4 impactDescription: prevents unauthorized access to server mutations
5 tags: server, server-actions, authentication, security, authorization
6 ---
7
8 ## Authenticate Server Actions Like API Routes
9
10 **Impact: CRITICAL (prevents unauthorized access to server mutations)**
11
12 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.
13
14 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."
15
16 **Incorrect (no authentication check):**
17
18 ```typescript
19 'use server'
20
21 export async function deleteUser(userId: string) {
22 // Anyone can call this! No auth check
23 await db.user.delete({ where: { id: userId } })
24 return { success: true }
25 }
26 ```
27
28 **Correct (authentication inside the action):**
29
30 ```typescript
31 'use server'
32
33 import { verifySession } from '@/lib/auth'
34 import { unauthorized } from '@/lib/errors'
35
36 export async function deleteUser(userId: string) {
37 // Always check auth inside the action
38 const session = await verifySession()
39
40 if (!session) {
41 throw unauthorized('Must be logged in')
42 }
43
44 // Check authorization too
45 if (session.user.role !== 'admin' && session.user.id !== userId) {
46 throw unauthorized('Cannot delete other users')
47 }
48
49 await db.user.delete({ where: { id: userId } })
50 return { success: true }
51 }
52 ```
53
54 **With input validation:**
55
56 ```typescript
57 'use server'
58
59 import { verifySession } from '@/lib/auth'
60 import { z } from 'zod'
61
62 const updateProfileSchema = z.object({
63 userId: z.string().uuid(),
64 name: z.string().min(1).max(100),
65 email: z.string().email()
66 })
67
68 export async function updateProfile(data: unknown) {
69 // Validate input first
70 const validated = updateProfileSchema.parse(data)
71
72 // Then authenticate
73 const session = await verifySession()
74 if (!session) {
75 throw new Error('Unauthorized')
76 }
77
78 // Then authorize
79 if (session.user.id !== validated.userId) {
80 throw new Error('Can only update own profile')
81 }
82
83 // Finally perform the mutation
84 await db.user.update({
85 where: { id: validated.userId },
86 data: {
87 name: validated.name,
88 email: validated.email
89 }
90 })
91
92 return { success: true }
93 }
94 ```
95
96 Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)
97
97 lines MARKDOWN