Actions와 보안

662025년 10월 20일3

서버 액션(Server Actions)은 개발자에게 전례 없는 편리함을 제공했지만, 그 편리함의 이면에는 반드시 해결해야 할 보안이라는 거대한 과제가 있었다. 클라이언트에서 시작된 요청이 서버의 함수를 직접 실행하는 것처럼 보이는 구조는, 그 자체로 수많은 공격 벡터에 노출될 수 있었기 때문이다.

React Core Team은 보안 전문가들과 함께, 서버 액션이 기본적으로 안전하게 동작하도록 만드는 다층적인 방어 메커니즘을 설계했다.

가장 먼저 고려된 위협은 ‘사이트 간 요청 위조(CSRF, Cross-Site Request Forgery)’ 공격이었다. 이는 악의적인 웹사이트가 사용자의 인증 정보(쿠키 등)를 이용하여, 사용자가 의도하지 않은 요청(예: 비밀번호 변경, 글 삭제)을 다른 사이트(사용자의 애플리케이션)로 보내는 공격 기법이다.

“서버 액션은 기본적으로 POST 요청을 사용하므로, GET 요청보다 CSRF에 덜 취약하긴 합니다. 하지만 그것만으로는 충분하지 않습니다.”

보안팀의 한 엔지니어가 단호하게 말했다. “우리는 모든 액션 요청이 우리 애플리케이션의 UI를 통해 정당하게 시작되었음을 증명할 수 있어야 합니다.”

이를 위해, React는 서버 액션을 사용하는 모든 <form>이 렌더링될 때, 보이지 않는 암호화된 토큰을 함께 생성하여 폼 데이터에 포함시켰다. 이 토큰은 사용자의 세션과 연결된, 일회성에 가까운 고유한 값이었다.

서버는 액션 요청을 받으면, 가장 먼저 이 숨겨진 토큰을 검증했다. 만약 토큰이 없거나, 유효하지 않거나, 이미 사용된 토큰이라면, 서버는 해당 요청을 즉시 거부하고 액션 실행을 차단했다. 이 간단하지만 강력한 메커니즘을 통해, 외부 사이트에서 위조된 요청은 원천적으로 차단되었다.

두 번째 과제는 ‘데이터 변조(Data Tampering)’였다. 클라이언트가 서버 액션 함수를 직접 호출하는 것처럼 보이지만, 실제로는 중간에 네트워크를 거친다. 악의적인 사용자가 이 네트워크 요청을 가로채, 서버로 전송되는 데이터를 조작할 가능성이 있었다.

예를 들어, 상품 구매 액션에서 상품 ID나 가격을 조작하여 전송하는 시나리오였다.

이 문제를 해결하기 위해, React는 ‘클로저(Closure)’의 개념을 활용했다. 서버 컴포넌트에서 서버 액션을 정의할 때, 액션은 자신이 정의된 시점의 서버 사이드 변수들을 ‘기억’할 수 있었다.

// ProductPage.server.js
import { purchaseItem } from './actions';

async function ProductPage({ productId }) {
  const product = await db.product.findUnique({ where: { id: productId } });
  const price = product.price; // 서버에서 조회한 신뢰할 수 있는 가격

  // 액션에 서버 사이드 변수를 바인딩
  const purchaseAction = purchaseItem.bind(null, productId, price);

  return <BuyButton action={purchaseAction} />;
}

여기서 purchaseItem 액션은 클라이언트로부터 어떤 데이터를 받는 대신, 서버에서 직접 조회한 productIdprice 값을 미리 바인딩(bind)했다. 클라이언트는 그저 이 미리 포장된 액션을 실행시키는 역할만 할 뿐, 중요한 데이터를 조작할 기회 자체가 없었다.

이처럼 React는 보이지 않는 곳에서, 개발자가 별도의 보안 코드를 작성하지 않아도 서버 액션이 안전하게 동작하도록 만드는 여러 장치들을 마련해두었다. 암호화된 토큰으로 요청의 출처를 확인하고, 클로저와 바인딩을 통해 데이터의 무결성을 보장했다.

개발자들은 그저 'use server'라고 선언하고 액션을 작성하면 그만이었다. 복잡하고 까다로운 보안 문제의 책임은 이제 React 프레임워크가 짊어지게 되었다. 이것은 편리함과 안전함이라는 두 마리 토끼를 모두 잡으려는, React 19의 깊은 고민이 낳은 결과물이었다.