C.W.K.
Stream
Lesson 08 of 08 · published

Security & Best Practice

~22 min · security, CSRF, auth, closures

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

공짜로 받는 것

  • CSRF protection — Origin header validation + per-build encrypted action ID.
  • Dead-code elimination — unused Server Action 이 build 에 안 들어감.
  • POST-only — GET-by-URL 노출 없음.
  • Encrypted closure — inline action 이 capture 한 값이 client 에 action reference 도착 전에 encrypt.

본인이 추가해야 할 것

RiskMitigation
믿을 수 없는 input모든 field server-side validate (Zod 또는 동등)
잘못된 user 가 다른 사람 data mutateSession 읽고 write 전에 resource ownership check
Rate abuseIP/user 별 rate-limit (Upstash, Redis, edge middleware)
Closure 안 sensitive secretSecret 직접 capture 피하고 identifier 넘긴 후 server 에서 re-fetch

Code

Auth check 항상, validate 항상·ts
'use server';
import { z } from 'zod';
import { auth } from '@/lib/auth';
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';

const Schema = z.object({ postId: z.string().uuid() });

export async function deletePost(formData: FormData) {
  const session = await auth();
  if (!session) throw new Error('Unauthorized');

  const parsed = Schema.safeParse({ postId: formData.get('postId') });
  if (!parsed.success) throw new Error('Bad input');

  const post = await db.post.findUnique({ where: { id: parsed.data.postId } });
  if (post?.authorId !== session.user.id) throw new Error('Forbidden');

  await db.post.delete({ where: { id: parsed.data.postId } });
  revalidatePath('/posts');
}
Closure encrypt 됨, but identifier 활용·tsx
// Secret capture 하는 inline action — encrypt 됐지만 re-fetch 가 더 깔끔
export default async function Page() {
  const apiKey = await getApiKey(); // server-only

  async function callExternal(formData: FormData) {
    'use server';
    const value = formData.get('value') as string;
    await fetch('https://external/api', {
      method: 'POST',
      headers: { Authorization: `Bearer ${apiKey}` }, // capture + encrypt
      body: JSON.stringify({ value }),
    });
  }
  return <form action={callExternal}>{/* … */}</form>;
}

External links

Exercise

Server Action 한 개를 위 4 가지 risk 위해 audit. Zod validation, auth check, ownership 검증, rate-limit hook 추가. 변경 문서화.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.