스팟에디터는 카카오 소셜 로그인을 기반으로 한 JWT 인증 시스템을 사용한다.
토큰은 모두 httpOnly 쿠키에 저장되며, 이 방식은 보안성과 사용자 경험(자동 로그인 유지)에 중점을 두었다.
Access Token(AT)과 Refresh Token(RT)을httpOnly 쿠키로 프론트엔드에 전달한다.즉, 프론트는 토큰을 직접 다루지 않아도 되고, 프론트는
withCredentials: true만 설정하면 인증이 처리된다.
먼저 XSS와 CSRF에 대해 알아보자
공격자가 사이트에 악성 스크립트를 삽입해서 다른 사용자의 브라우저에서 실행되도록 만드는 공격
<script>alert('해킹');</script> 같은 코드를 써서 올림사용자가 로그인 상태인 걸 악용해서 사용자가 의도하지 않은 요청을 서버에 보내는 공격
<img src="https://bank.com/transfer?to=attacker&amount=1000"> 요청이 실행됨XSS 공격과 CSRF 공격의 차이
XSS 공격 (사이트 간 스크립팅) 공격은 사용자가 웹사이트를 신용하여 악성 스크립트가 실행된다면, CSRF 공격은 반대로 특정 웹사이트가 사용자의 브라우저를 신용하여 발생하는 공격이다. 간단히 말해서, XSS 공격은 악성 코드가 클라이언트에서 발생하는데 반에, CSRF 공격은 악성 코드가 서버에서 발생한다고 볼 수 있다.
아무리 다른 공격(CSRF 등)에 대한 방비를 열심히 해도 XSS가 뚫린다면 아무 소용이 없다.
Js 코드로 의도하지 않은 request를 날린다던가
localStorage, 변수 값 등 모든 것이 탈취 가능하기 때문이다.
XSS 공격 방지는 웹 보안의 뿌리라고 할 수 있다.
크게 2가지가 있다.
장점: CSRF 공격에는 안전
자동으로 request에 담기는 쿠키와는 다르게
js 코드에 의해 헤더에 담기므로 XSS를 뚫지 않는 이상
공격자가 정상적인 사용자인 척 request를 보내기가 어렵다.
단점: XSS에 취약
공격자가 localStorage에 접근하는 Js 코드 한 줄만 주입하면
localStorage를 공격자가 내 집처럼 드나들 수 있다.
장점: XSS 공격으로부터 localStorage에 비해 안전
쿠키의 httpOnly 옵션을 사용하면 Js에서 쿠키에 접근 자체가 불가능하다.
그래서 XSS 공격으로 쿠키 정보를 탈취할 수 없다.
(httpOnly 옵션은 서버에서 설정할 수 있음)
단점: CSRF 공격에 취약
자동으로 http request에 담아서 보내기 때문에
공격자가 request url만 안다면
사용자가 관련 link를 클릭하도록 유도하여 request를 위조하기 쉽다.
httpOnly 쿠키를 선택했다.
CSRF 공격은 다루기 쉬운 반면 프론트엔드 크기가 크면 클수록
XSS 공격을 막기위한 작업은 많아지므로 쿠키 사용이 나아보였다.
쿠키는 별도로 헤더에 담지 않아도 요청을 보낼때 자동으로 담아서 보내지기 때문에
매번 서버로의 요청에 담아야하는 토큰이라는 성격과 잘 맞고, 코드도 더 간결하게 작성할 수 있게 되는 것으로 보인다.
비로그인 사용자가 처음으로 스팟에디터에 진입했을 때,
홈 화면에서 일부 컴포넌트가 사용하는 axios 요청이 실행되며,
그 요청에 붙은 인터셉터가 /api/auth/refresh를 호출하게 된다.
하지만 이 사용자는 로그인하지 않았기 때문에 쿠키가 없어 refresh는 무의미한 시도가 되고,
결과적으로 불필요한 인증 관련 요청이 1회 발생하는 셈이다.
httpOnly 쿠키는 JS에서 접근 불가 → 토큰이 존재하는지 여부를 클라이언트에서 판단할 수 없음// AuthClient 내부
private isAuthenticated: boolean | null = null;
async fetchAuthStatus() {
try {
await this.instance.get('/api/users');
this.isAuthenticated = true;
} catch {
this.isAuthenticated = false;
}
}
/api/users 요청을 통해 로그인 여부를 한 번 판단해 isAuthenticated에 저장if (this.isAuthenticated === false) {
// 로그인 안된 사용자 → refresh 시도 X
return Promise.reject(error);
}
/로 인증 상태 확인enabled: false 등으로 조건부 쿼리 사용)사용자가 로그인한 상태에서 탭을 닫고,
나중에 다시 직접 주소를 입력해서 접속하는 시나리오를 생각해보자.
이때 쿠키는 브라우저에 남아있기 때문에,
/api/users같은 요청을 통해 자동으로 로그인 상태를 복구할 수 있다.
반대로 쿠키가 없으면 401 → 로그인 페이지로 안내하면 된다.
→ 따라서 이 인증 요청 1번은 보안성과 UX를 모두 챙기는 자연스러운 흐름이라고도 볼 수 있다.
스팟에디터는 보안과 사용성을 고려해 httpOnly 쿠키 기반 JWT 인증을 채택했고,
초기 인증 요청 1회는 필연적으로 발생하지만, 이를 통해 로그인 상태를 안정적으로 판단할 수 있다.
불필요한 인증 요청은 상태 기반 인증 흐름과 인터셉터 제어, axios 인스턴스 분리 등을 통해 충분히 관리할 수 있다.