바이브코딩할 때 꼭 알아야 할 로그인 저장소 - LocalStorage, Cookie, HttpOnly Cookie 차이
AI에게 로그인 기능을 맡겼다면 꼭 확인해야 할 보안 상식! LocalStorage, Cookie, HttpOnly Cookie의 차이와 아이디 저장, Access Token, Refresh Token은 어디에 저장해야 하는지 실제 서비스 관점에서 쉽게 정리했습니다. 로그인은 '되는 것'보다 '안전하게 되는 것'이 더 중요합니다.
로그인 기능을 개발하다 보면 이런 데이터를 어디에 저장해야 할지 고민하게 됩니다.
- 아이디 저장
- 로그인 상태 유지
- Access Token
- Refresh Token
- Session ID
겉으로 보기에는 모두 브라우저에 저장되는 데이터처럼 보이지만, 저장 방식과 보안 수준은 크게 다릅니다.
특히 AI가 만들어준 로그인 코드를 그대로 사용하다 보면 토큰을 LocalStorage에 저장하는 경우가 많은데, 실제 서비스를 운영할 계획이라면 저장 위치를 한 번 더 확인할 필요가 있습니다.
로그인이 되는 것과 안전하게 로그인되는 것은 전혀 다른 문제이기 때문입니다.
1. LocalStorage
LocalStorage는 브라우저가 제공하는 저장 공간입니다.
한 번 저장된 데이터는 새로고침하거나 브라우저를 종료해도 그대로 남아 있습니다.
대표적으로 아래와 같은 데이터를 저장할 때 사용합니다.
- 아이디 저장
- 다크모드 설정
- 최근 검색어
- 사용자 환경설정
예시는 다음과 같습니다.
localStorage.setItem("userId", "kai");
저장된 값은 JavaScript로 다시 읽을 수 있습니다.
const userId = localStorage.getItem("userId");
장점
- 구현이 간단합니다.
- 브라우저를 종료해도 데이터가 유지됩니다.
- 일반 Cookie보다 저장 용량이 큽니다.
- 서버 요청마다 자동으로 전송되지 않습니다.
주의할 점
LocalStorage의 가장 큰 특징이자 단점은 JavaScript에서 자유롭게 접근할 수 있다는 점입니다.
localStorage.getItem("token");
사이트에 XSS 취약점이 존재해 악성 JavaScript가 실행된다면, LocalStorage에 저장된 토큰이 외부로 전송될 수 있습니다.
따라서 아래와 같은 민감한 정보는 신중하게 저장해야 합니다.
- Access Token
- Refresh Token
- Session 정보
- 비밀번호
특히 비밀번호는 LocalStorage뿐 아니라 브라우저의 다른 저장 공간에도 직접 저장하면 안 됩니다.
2. Cookie
Cookie 역시 브라우저에 데이터를 저장하는 방식입니다.
LocalStorage와 가장 큰 차이는 조건에 맞는 서버 요청을 보낼 때 Cookie가 자동으로 함께 전송된다는 점입니다.
예를 들어 로그인 후 서버가 아래와 같은 Cookie를 설정할 수 있습니다.
Set-Cookie: session=abc123
이후 브라우저는 같은 서비스로 요청을 보낼 때 해당 Cookie를 자동으로 포함합니다.
Cookie: session=abc123
장점
- 서버가 Cookie를 자동으로 받을 수 있습니다.
- 세션 로그인 구현에 적합합니다.
- 만료 시간, 도메인, 경로 등을 지정할 수 있습니다.
주의할 점
일반 Cookie는 JavaScript에서 접근할 수 있습니다.
document.cookie;
따라서 일반 Cookie에 중요한 토큰을 저장하면 XSS 공격으로 내용이 노출될 가능성이 있습니다.
또한 Cookie는 요청에 자동으로 포함되기 때문에 CSRF 공격에 대한 대비도 필요합니다.
Cookie를 사용한다고 해서 자동으로 안전해지는 것은 아닙니다.
3. HttpOnly Cookie
HttpOnly Cookie는 별도의 저장 공간이 아닙니다.
일반 Cookie에 HttpOnly 보안 옵션이 추가된 형태입니다.
서버에서 다음과 같이 설정할 수 있습니다.
Set-Cookie: refreshToken=abc123; HttpOnly
HttpOnly가 설정된 Cookie도 브라우저에 저장되고 서버 요청에 자동으로 포함됩니다.
하지만 JavaScript에서는 읽을 수 없습니다.
document.cookie;
위 코드를 실행해도 HttpOnly Cookie는 반환되지 않습니다.
즉 HttpOnly Cookie는 브라우저와 서버가 사용하지만, 웹페이지의 JavaScript는 직접 접근할 수 없습니다.
그래서 주로 아래와 같은 데이터를 저장하는 데 사용합니다.
- Refresh Token
- Session ID
- 로그인 세션 Cookie
HttpOnly Cookie가 중요한 이유
사이트에서 악성 JavaScript가 실행된다고 가정해보겠습니다.
일반 Cookie나 LocalStorage에 토큰이 저장되어 있다면 다음과 같은 방식으로 값을 읽으려 할 수 있습니다.
const token = localStorage.getItem("token");
또는
const cookies = document.cookie;
하지만 HttpOnly Cookie는 JavaScript에서 읽을 수 없기 때문에 이런 방식으로 값을 직접 탈취하기 어렵습니다.
그렇다고 HttpOnly가 XSS 공격 자체를 막아주는 것은 아닙니다.
공격자가 사용자의 브라우저에서 요청을 실행할 수는 있으므로 아래와 같은 보안 설정도 함께 고려해야 합니다.
- Secure
- SameSite
- CSRF 방어
- Content Security Policy
- 입력값 검증 및 출력값 이스케이프 처리
HttpOnly는 만능 보안 기능이 아니라, 토큰 탈취 위험을 줄여주는 중요한 방어 수단 중 하나입니다.
Secure 옵션
Secure가 설정된 Cookie는 HTTPS 연결에서만 전송됩니다.
Set-Cookie: refreshToken=abc123; HttpOnly; Secure
실제 운영 서비스라면 로그인 관련 Cookie에 HttpOnly와 Secure를 함께 사용하는 경우가 많습니다.
개발 환경에서는 HTTP를 사용하기도 하지만, 운영 환경에서는 HTTPS 사용을 기본으로 생각하는 것이 좋습니다.
SameSite 옵션
SameSite는 다른 사이트에서 발생한 요청에 Cookie를 얼마나 허용할지 결정하는 옵션입니다.
대표적으로 다음과 같은 값이 있습니다.
SameSite=Strict
다른 사이트에서 넘어오는 요청에는 Cookie를 거의 보내지 않습니다.
보안은 강하지만 외부 링크나 일부 로그인 흐름에서 불편이 생길 수 있습니다.
SameSite=Lax
일반적인 링크 이동에는 Cookie를 허용하면서, 일부 위험한 외부 요청은 제한합니다.
많은 서비스에서 기본적인 선택으로 사용합니다.
SameSite=None
다른 사이트에서 발생한 요청에도 Cookie를 전송합니다.
소셜 로그인이나 서로 다른 도메인을 연결해야 할 때 사용할 수 있습니다.
이 경우에는 반드시 Secure 옵션도 함께 설정해야 합니다.
Set-Cookie: refreshToken=abc123; HttpOnly; Secure; SameSite=None
아이디 저장은 어디에 해야 할까?
로그인 화면의 아이디 저장 기능은 보통 LocalStorage나 일반 Cookie를 사용할 수 있습니다.
localStorage.setItem("savedUserId", "kai@example.com");
아이디는 비밀번호나 로그인 토큰처럼 인증 권한을 직접 제공하는 정보는 아니기 때문입니다.
다만 이메일 주소나 전화번호가 아이디라면 개인정보에 해당할 수 있으므로 공용 기기에서는 노출될 가능성을 안내하는 것이 좋습니다.
그리고 아이디 저장 기능과 자동 로그인은 다른 기능입니다.
아이디 저장
로그인 화면에 아이디를 다시 채워주는 기능입니다.
사용자는 비밀번호를 입력하고 다시 로그인해야 합니다.
자동 로그인
세션이나 Refresh Token을 이용해 로그인 상태를 계속 유지하는 기능입니다.
아이디를 LocalStorage에 저장했다고 자동 로그인이 구현되는 것은 아닙니다.
실제 서비스에서는 어떻게 나눌까?
일반적인 구성은 다음과 같습니다.
| 저장할 데이터 | 추천 저장 위치 |
|---|---|
| 아이디 저장 | LocalStorage 또는 일반 Cookie |
| 다크모드 설정 | LocalStorage |
| 최근 검색어 | LocalStorage |
| Access Token | 메모리 또는 짧은 수명으로 관리 |
| Refresh Token | HttpOnly Cookie |
| Session ID | HttpOnly Cookie |
| 비밀번호 | 저장하지 않음 |
Access Token을 반드시 메모리에만 저장해야 한다는 절대 규칙이 있는 것은 아닙니다.
서비스 구조에 따라 Access Token까지 HttpOnly Cookie로 관리하기도 하고, Access Token은 메모리에 두고 Refresh Token만 HttpOnly Cookie에 저장하기도 합니다.
중요한 것은 AI가 만들어준 방식을 그대로 사용하는 것이 아니라, 어떤 위험을 감수하고 어떤 방식으로 방어할지 이해하고 선택하는 것입니다.
바이브코딩할 때 AI에게 이렇게 요청해보세요
로그인 기능을 요청할 때 단순히 다음처럼 말하면
JWT 로그인 기능을 만들어줘.
AI가 편의를 위해 토큰을 LocalStorage에 저장하는 코드를 생성할 수 있습니다.
조금 더 구체적으로 요청하는 것이 좋습니다.
Refresh Token은 HttpOnly, Secure, SameSite가 적용된 Cookie에 저장하고,
Access Token은 짧은 수명으로 관리해줘.
비밀번호와 토큰은 LocalStorage에 저장하지 말고,
CSRF와 XSS 위험도 고려해서 로그인 구조를 설계해줘.
프론트엔드와 백엔드 도메인이 다르다면 CORS, Cookie Domain, SameSite 설정도 함께 요청해야 합니다.
프론트엔드와 API 서버의 도메인이 다른 환경이야.
HttpOnly Cookie가 정상적으로 전송되도록
CORS credentials 설정, SameSite, Secure, Cookie Domain까지 고려해줘.
AI가 만든 로그인 코드에서 확인할 부분
로그인 기능이 완성되었다면 최소한 아래 항목은 확인해보는 것이 좋습니다.
- 비밀번호를 LocalStorage나 Cookie에 저장하지 않는가?
- Refresh Token을 JavaScript가 읽을 수 있는 곳에 저장하지 않는가?
- HttpOnly 옵션이 적용되었는가?
- 운영 환경에서 Secure 옵션이 적용되었는가?
- 서비스 구조에 맞는 SameSite 설정을 사용했는가?
- Refresh Token의 유효기간이 지나치게 길지 않은가?
- 로그아웃 시 서버에서도 토큰이나 세션이 무효화되는가?
- 토큰 재발급 시 기존 Refresh Token을 계속 재사용하지 않는가?
- CORS 설정에서 모든 출처를 무분별하게 허용하지 않는가?
- XSS와 CSRF에 대한 별도 방어가 적용되어 있는가?
마무리
LocalStorage, Cookie, HttpOnly Cookie는 모두 브라우저에서 사용하는 저장 방식이지만 목적과 보안 특성이 다릅니다.
간단히 정리하면 다음과 같습니다.
- 화면 설정이나 아이디 저장은 LocalStorage
- 서버 요청에 자동으로 포함해야 하는 데이터는 Cookie
- JavaScript에서 읽으면 안 되는 로그인 정보는 HttpOnly Cookie
바이브코딩에서는 로그인 기능 자체를 만드는 시간은 크게 줄었습니다.
하지만 AI가 생성한 코드가 어떤 방식으로 토큰을 저장하고 있는지는 직접 확인해야 합니다.
로그인이 정상적으로 동작한다고 해서 안전하게 구현된 것은 아닙니다.
저장 위치 하나가 서비스의 보안을 결정할 수도 있습니다.