# Authenticate Server Actions Like API Routes

> Authenticate server actions with the same rigor as API routes (session, token, CSRF). Use when implementing or securing Next.js server actions.

- Skill: `ihj04982/authenticate-server-actions-like-api-routes` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ihj04982/authenticate-server-actions-like-api-routes`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ihj04982/authenticate-server-actions-like-api-routes/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: ihj04982 (https://skillmd.com/u/ihj04982)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ihj04982/authenticate-server-actions-like-api-routes

---


## 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)

