React 앱 보안 체크리스트: XSS·CSRF·토큰
React 애플리케이션의 XSS 차단, CSRF 보호, 토큰 관리 등 핵심 보안 항목을 정리한 실무용 체크리스트
목차
개요
웹 애플리케이션 보호는 여러 층으로 이뤄진다. 프론트엔드에서는 입력 검증, 출력 이스케이프, 안전한 토큰 관리가 중요하다. 이 글은 React 환경에서 흔히 마주치는 취약점(XSS, CSRF)과 토큰 보안 관리를 초보자도 이해하기 쉽게 정리한 체크리스트다. 실무에서 바로 적용 가능한 권장 사항을 중심으로 설명한다.
XSS(교차 사이트 스크립트) 방지
XSS는 사용자 입력이 검증되지 않은 채로 렌더링될 때 발생한다. React는 기본적으로 JSX 출력에서 자동으로 이스케이프를 수행한다. 하지만 다음과 같은 경우 주의가 필요하다.
주의 지점
- dangerouslySetInnerHTML 사용
- 서버에서 렌더링된 HTML을 직접 삽입
- 외부 라이브러리의 템플릿 사용
권장 조치
- 가능하면 dangerouslySetInnerHTML 사용을 피한다.
- 외부 HTML을 삽입해야 하면 신뢰 가능한 소스만 허용하고, 서드파티 라이브러리로 정교하게 sanitize한다.
- 입력값은 서버와 클라이언트 양쪽에서 검증한다.
코드 예시: 안전한 innerHTML 처리
/* 외부 HTML을 삽입할 때 DOMPurify 같은 라이브러리로 정제 */
import DOMPurify from 'dompurify';
function SafeHtml({ html }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
CSRF(사이트 간 요청 위조) 보호
CSRF는 사용자의 인증 상태를 악용해 공격자가 의도치 않은 요청을 서버에 보내는 공격이다. 특히 쿠키 기반 인증을 사용할 때 취약하다. React에서 CSRF 보호는 주로 다음 방법으로 구현한다.
권장 조치
- 상호 검증 가능한 CSRF 토큰 사용(서버 발급, 요청 헤더 포함)
- SameSite 속성을 사용한 안전한 쿠키 설정
- 중요한 상태 변경 요청은 POST/PUT/DELETE로 제한하고 CORS 정책을 적용
코드 예시: CSRF 토큰을 헤더에 포함해 요청 보내기
// 클라이언트: fetch로 요청 보낼 때 CSRF 토큰 헤더 추가
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/api/secure-action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ data: 'value' })
});
// 서버는 'X-CSRF-Token' 헤더를 확인해 유효성 검사 수행
토큰 관리 전략 (JWT, 액세스/리프레시 토큰)
토큰 관리는 인증 전체의 핵심이다. 잘못된 저장 방식이나 갱신 로직은 탈취 위험을 높인다. 다음 원칙을 권장한다.
권장 원칙
- 가능하면 민감한 토큰은 httpOnly, Secure 쿠키에 저장
- 로컬 스토리지에 토큰을 오래 저장하지 않는다(특히 XSS 위험이 있는 경우)
- 액세스 토큰은 짧은 수명으로, 리프레시 토큰으로 갱신 흐름을 구성
- 토큰 탈취 시를 대비해 서버 측에서 세션 무효화(블랙리스트) 지원
예시: 안전한 토큰 갱신 플로우
서버는 리프레시 토큰을 httpOnly 쿠키로 발급한다. 클라이언트는 액세스 토큰이 만료되면 자동으로 갱신 엔드포인트를 호출한다.
// 클라이언트: 액세스 토큰이 만료되면 리프레시 엔드포인트 호출
async function fetchWithAutoRefresh(url, options = {}) {
let res = await fetch(url, options);
if (res.status === 401) { // 액세스 토큰 만료 가능성
const refreshRes = await fetch('/auth/refresh', { method: 'POST', credentials: 'include' });
if (refreshRes.ok) {
// 서버가 새 액세스 토큰을 응답으로 보냄(예: JSON)
const { accessToken } = await refreshRes.json();
options.headers = { ...options.headers, Authorization: `Bearer ${accessToken}` };
res = await fetch(url, options); // 재시도
}
}
return res;
}
로컬 스토리지 사용 시 주의
- 로컬 스토리지는 XSS에 취약하다. 민감 정보 저장을 피함
- 부득이 사용할 경우 최소 권한 원칙과 짧은 만료 시간 적용
- 토큰 접근 로그와 비정상 패턴 모니터링을 도입
추가 권장 보안 항목
입력 검증과 출력 이스케이프
모든 사용자 입력은 화이트리스트 방식으로 검증한다. 출력 시 값은 항상 이스케이프하거나 서식화 도구를 사용해 안전하게 렌더링한다. 서버와 클라이언트 양쪽에서 중복 검증하면 안전성이 높아진다.
콘텐츠 보안 정책(CSP)
CSP를 통해 외부 스크립트의 실행을 제한하면 XSS 위험을 크게 줄일 수 있다. 개발 중에는 정책을 넉넉히 두고, 배포 시 더 엄격하게 조정한다.
에러와 로깅
민감한 정보를 클라이언트 에러 메시지에 노출하지 않는다. 서버는 실패 로그를 수집하되 토큰이나 비밀번호 같은 민감 필드를 마스킹한다.
체크리스트 요약
- React 컴포넌트에서 dangerouslySetInnerHTML 사용 최소화
- 외부 HTML은 신뢰 검증과 sanitize 처리
- CSRF 토큰을 헤더로 전달하고 SameSite 쿠키 사용
- 토큰은 가능하면 httpOnly 쿠키로 저장, 로컬 스토리지는 최소화
- 액세스 토큰 짧은 수명, 리프레시 토큰으로 안전하게 갱신
- CSP 적용과 입력 화이트리스트 검증 병행
- 에러/로그에서 민감 정보 노출 차단
마무리
보안은 단발성이 아닌 지속적인 과정이다. 위 체크리스트를 바탕으로 우선순위가 높은 항목부터 단계적으로 적용한다. 또한 배포 후에도 취약점 스캔과 모니터링을 꾸준히 수행하면 보안 수준을 유지하기 쉽다. 필요하면 각 항목을 팀의 코드 리뷰 기준으로 정리해 일상적인 개발 흐름에 통합한다.