브라우저와 Web 기본

3줄 요약

  • 브라우저는 단순 HTTP 클라이언트가 아니라 보안 정책, 렌더링, 이벤트, storage, cookie 제약을 동시에 가진 사용자 측 실행 환경이다.
  • 백엔드 API가 정상이어도 CORS, Cookie, SameSite, preflight, mixed content 때문에 브라우저에서만 실패할 수 있다.
  • 개인 프로젝트에서도 DOM, 이벤트, HTTP, CORS, Cookie, Web Storage의 경계를 알아야 인증과 API 연동을 안정적으로 만들 수 있다.

핵심 정리

  • HTML은 문서 구조, CSS는 표현, JavaScript는 상호작용과 브라우저 API 제어를 맡는다.
  • HTML5 이후 WebSocket, Geolocation, Storage, Canvas 등 Web Platform API가 크게 확장되었지만, 실제 앱 품질은 “무엇을 할 수 있는가”보다 “브라우저가 무엇을 막는가”를 이해하는 데서 자주 갈린다.
  • DOM은 문서 객체 모델이고, 이벤트는 사용자 입력과 브라우저 상태 변화를 앱 코드로 전달한다.
  • CORS는 서버 간 통신 문제가 아니라 브라우저가 cross-origin 응답을 JavaScript에 노출할지 제한하는 정책이다.
  • Cookie는 요청에 자동 첨부될 수 있고, Web Storage는 JavaScript가 직접 읽고 쓴다.

백엔드 개발자가 헷갈리는 지점

  • Postman에서 되는 API가 브라우저에서도 당연히 된다고 생각하기 쉽다.
    • 판단 기준은 브라우저가 origin, credentials, preflight를 검사한다는 점이다.
    • CORS 응답 헤더와 Cookie 속성을 백엔드에서 맞춰야 한다.
  • Cookie와 localStorage를 단순 저장소 차이로만 보기 쉽다.
    • 판단 기준은 자동 전송, JavaScript 접근 가능성, XSS/CSRF 위험이다.
    • refresh token은 JavaScript 접근 가능 저장소에 두지 않는 편이 안전하다.
  • DOM 조작을 직접 많이 하면 React와 충돌할 수 있다는 점을 놓치기 쉽다.
    • 판단 기준은 React가 UI를 소유하는 영역과 직접 DOM API가 필요한 영역을 나누는 것이다.

개인 프로젝트 기준

확인 질문

  • CORS 오류는 왜 Postman에서는 재현되지 않을 수 있는가?
    • CORS는 브라우저가 강제하는 정책이기 때문이다.
  • Cookie와 Web Storage의 큰 차이는 무엇인가?
    • Cookie는 조건이 맞으면 요청에 자동 첨부되고, Web Storage는 JavaScript가 직접 읽고 쓴다.
  • 브라우저 기본을 백엔드 개발자가 알아야 하는 이유는 무엇인가?
    • API 인증, CORS, Cookie, storage, 사용자 이벤트가 백엔드 계약과 직접 연결되기 때문이다.

01. 브라우저 HTTP DOM 상세

이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 브라우저는 왜 Postman과 다르게 API 요청을 실패시킬 수 있는가?
  • Cookie, Web Storage, CORS는 인증 설계와 어떻게 연결되는가?
  • DOM과 이벤트를 React 앱에서도 알아야 하는 이유는 무엇인가?

개요

브라우저는 HTML, CSS, JavaScript를 실행하는 플랫폼이다. 예전에는 문서를 보여주는 도구처럼 느껴졌지만, 현재의 Web Platform은 WebSocket, Geolocation, Storage, Media, Notification 등 다양한 API를 제공한다. 그렇다고 해서 브라우저를 단순한 범용 런타임처럼 보면 안 된다. 브라우저는 사용자를 보호하기 위해 origin, Cookie, storage, permission, mixed content 같은 보안 제약을 강하게 적용하고, 이 제약은 백엔드 API 설계와 배포 도메인 결정에 직접 영향을 준다.

원리

origin은 scheme, host, port의 조합이다. https://app.example.comhttps://api.example.com은 host가 다르므로 다른 origin이다. 브라우저는 다른 origin의 응답을 마음대로 JavaScript가 읽지 못하게 CORS 정책을 적용한다. 서버가 적절한 CORS 헤더를 주지 않으면 네트워크 요청 자체는 서버에 도달했더라도 브라우저가 응답 본문을 애플리케이션 코드에 넘기지 않는다.

Cookie는 브라우저가 조건에 따라 요청에 자동 첨부한다. HttpOnly Cookie는 JavaScript로 읽을 수 없어 XSS 토큰 탈취 위험을 줄인다. 반대로 localStoragesessionStorage는 JavaScript에서 접근하기 쉽지만, XSS가 있으면 그대로 읽힐 수 있다.

DOM은 브라우저가 HTML 문서를 객체 트리로 표현한 것이다. React를 쓰면 DOM을 직접 조작하는 일이 줄지만, focus, scroll, file input, canvas, third-party widget처럼 직접 DOM API를 이해해야 하는 순간은 여전히 있다.

코드로 보는 핵심 예시

Cookie 기반 인증 API는 프론트와 백엔드 설정이 함께 맞아야 한다. 타입 검증은 예시를 짧게 하기 위한 userSchema로 표현했고, 실제 프로젝트에서는 Zod schema나 OpenAPI 생성 타입을 기준으로 둔다.

import { z } from "zod";
 
const userSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  nickname: z.string(),
});
 
export async function getCurrentUser() {
  const response = await fetch("https://api.example.com/me", {
    credentials: "include",
  });
 
  if (response.status === 401) {
    return null;
  }
 
  if (!response.ok) {
    throw new Error("사용자 정보를 불러오지 못했습니다.");
  }
 
  const body: unknown = await response.json();
  return userSchema.parse(body);
}

서버는 특정 origin과 credentials를 허용해야 한다.

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

브라우저 storage는 민감도 기준으로 나눈다.

const theme = localStorage.getItem("theme") ?? "system";
localStorage.setItem("theme", "dark");

theme처럼 탈취되어도 피해가 작은 UI 설정은 storage에 적합하다. refresh token처럼 탈취되면 계정 장악으로 이어질 수 있는 값은 다른 기준으로 판단한다.

백엔드와 맞춰야 할 지점

  • 프론트 origin, API origin, 개발 origin을 환경별로 정한다.
  • CORS allowed origin, allowed headers, methods, credentials, preflight cache를 맞춘다.
  • Cookie 기반 인증이면 SameSite, Secure, HttpOnly, Domain, Path를 배포 도메인 기준으로 정한다.
  • 파일 다운로드, 이미지, WebSocket, SSE는 일반 JSON API와 다른 브라우저 제약이 있는지 확인한다.

개인 프로젝트 기준

  • 로컬 개발에서는 Vite proxy로 same-origin처럼 만들거나, CORS를 명확히 설정한다.
  • localStorage에는 theme, 최근 검색어, non-sensitive preference 정도만 둔다.
  • 인증 문제를 디버깅할 때는 Application tab의 Cookie와 Network tab의 request/response header를 같이 본다.

실전 팁

  • CORS 오류는 프론트 코드 에러처럼 보이지만 해결 지점은 대부분 서버 응답 헤더다.
  • Cookie가 안 붙으면 credentials, SameSite, Secure, domain, HTTPS 여부를 같이 본다.
  • 직접 DOM 조작이 필요한 경우 React ref를 사용해 React가 소유하는 렌더링과 충돌하지 않게 한다.
  • 관련 판단은 토큰은 localStorage, sessionStorage, cookie 중 어디에 둘까?를 다시 본다.

위험 신호!

  • 운영 CORS에 *를 열어 두고 credentials를 쓰려 한다.
  • localStorage에 access token과 refresh token을 모두 저장한다.
  • Cookie가 브라우저에 저장되었는지 확인하지 않고 프론트 fetch 코드만 계속 수정한다.
  • React 앱에서 DOM을 직접 수정해 React 렌더링 결과와 어긋난다.

확인 질문

확인 질문

  • origin은 무엇으로 결정되는가?
    • scheme, host, port의 조합으로 결정된다.
  • CORS에서 credentials를 쓰려면 어떤 점을 조심해야 하는가?
    • 서버가 특정 origin을 허용해야 하고, 프론트 요청에는 credentials: "include"가 필요하다.
  • HttpOnly Cookie의 장점은 무엇인가?
    • JavaScript로 읽을 수 없어 XSS로 인한 토큰 탈취 위험을 줄인다.

참고 문서