본문으로 건너뛰기

보안

본 문서는 프론트엔드가 책임지는 보안 범위와 그 한계를 정의합니다.

1. 프론트엔드가 책임지는 범위

중요

인가는 서버의 책임입니다

클라이언트 코드는 전부 조작 가능합니다. 사용자는 개발자 도구로 변수를 바꾸고, 네트워크 요청을 직접 만들고, 번들을 읽을 수 있습니다. 따라서 프론트엔드의 어떤 장치도 데이터 접근을 통제하지 못합니다. 프론트엔드가 하는 일은 서버가 거부할 것을 미리 숨겨 사용자 경험을 개선하는 것과 브라우저 환경 고유의 위협에 대응하는 것입니다.

프론트엔드 책임 서버 책임
토큰의 안전한 보관과 전송 토큰 검증
XSS 방어 인가 판정
민감정보의 화면·로그 노출 방지 데이터 격리
접근 불가 화면의 사전 차단 (경험) 접근 불가 요청의 거부 (보안)

2. 인증 토큰

2.1 소유 위치

토큰의 보관과 조회는 shared/auth/가 단독으로 소유합니다. 다른 계층이 저장소에 직접 접근하는 것을 금지합니다.

shared/auth/
├── session.ts
├── auth-interceptor.ts
├── auth-guard.ts
└── index.ts
파일 역할
session.ts 토큰 보관과 조회
auth-interceptor.ts 요청에 토큰을 부착
auth-guard.ts 라우트 접근 사전 차단

entities/user를 만들지 않습니다. 토큰과 로그인 응답은 인증 맥락 전용이며 도메인 모델로 재사용되지 않습니다. entities는 거의 모든 계층이 임포트할 수 있는 위치이므로 인증 세부를 두면 변경 파급이 넓어집니다.

2.2 보관 위치

저장소 특성 판단
메모리 (변수) 새로고침 시 소실. XSS로 탈취되지 않음 액세스 토큰의 기본
sessionStorage 탭 단위 유지. XSS로 탈취 가능 새로고침 유지가 필요할 때
localStorage 영구 유지. XSS로 탈취 가능 리프레시 토큰에 사용 금지
httpOnly 쿠키 JS 접근 불가. CSRF 대응 필요 백엔드가 지원하면 최선

액세스 토큰은 메모리를 기본으로 합니다. 새로고침 시 유지가 필요하면 sessionStorage를 사용하고, 그 경우 XSS 발생 시 탈취된다는 점을 인지합니다.

리프레시 토큰을 localStorage에 두는 것을 금지합니다. 탈취되면 만료가 사실상 무의미해집니다.

백엔드가 httpOnly 쿠키 방식을 지원하면 그쪽을 우선합니다. 이는 백엔드 협의 사항이며 본 표준이 강제하지 않습니다.

2.3 전송

토큰 부착은 HTTP 인터셉터가 단독으로 수행합니다. 개별 요청 함수가 헤더를 직접 조립하는 것을 금지합니다.

// shared/auth/auth-interceptor.ts
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(SessionStore).accessToken();
  if (!token) return next(req);
  return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};

외부 도메인 요청에는 토큰을 부착하지 않습니다. 인터셉터가 요청 URL을 확인해 자사 API에만 부착합니다. 확인하지 않으면 서드파티 요청에 토큰이 새어 나갑니다.

2.4 만료 처리

401 응답을 받으면 세션을 정리하고 로그인 화면으로 이동합니다. 처리 위치는 인터셉터이며, 개별 화면이 401을 각자 처리하는 것을 금지합니다.

리프레시 토큰으로 재발급하는 경우, 동시에 발생한 여러 401이 각각 재발급을 시도하지 않도록 진행 중인 재발급을 공유합니다.

3. XSS

3.1 Angular의 기본 방어

Angular는 템플릿 보간({{ }})과 속성 바인딩에서 값을 자동으로 이스케이프합니다. 이 방어를 우회하지 않는 것이 첫 번째 규칙입니다.

금지 사유
bypassSecurityTrustHtmlbypassSecurityTrust* 호출 이스케이프를 명시적으로 끕니다
[innerHTML]에 서버 응답을 그대로 바인딩 서버가 신뢰 가능해도 서버가 받은 값은 사용자 입력입니다
ElementRef.nativeElement.innerHTML 직접 대입 Angular의 sanitizer를 거치지 않습니다
eval, new Function, setTimeout에 문자열 전달 임의 코드 실행 경로입니다

서식 있는 텍스트를 표시해야 하면 마크다운 등 제한된 형식을 파싱해 컴포넌트로 렌더링합니다. HTML 문자열을 주입하지 않습니다.

bypassSecurityTrust*가 불가피한 경우 ADR로 사유와 적용 범위를 기록합니다. 현재 승인된 예외는 ADR-0013의 빌드 시점 생성 문서 HTML 하나입니다.

예외를 판정하는 기준은 입력이 런타임에 외부에서 오는가입니다. 위 금지의 근거가 "서버가 신뢰 가능해도 서버가 받은 값은 사용자 입력"이므로, 저장소에 커밋된 원본을 빌드 시점에 변환한 결과에는 그 근거가 적용되지 않습니다. 반대로 요청 파라미터나 서버 응답이 그 문자열에 한 번이라도 결합되면 예외 조건을 잃습니다.

3.2 URL

사용자 입력이나 서버 값을 href·src에 바인딩할 때 javascript: 스킴을 차단합니다. Angular가 [href] 바인딩에서 일부를 정리하지만, 동적으로 조립한 URL은 스킴을 직접 검증합니다.

외부 링크에는 rel="noopener noreferrer"를 부여합니다. noopener가 없으면 새 창이 window.opener로 원본 창을 조작할 수 있습니다.

4. 콘텐츠 보안 정책 (CSP)

CSP 헤더는 서버 또는 호스팅 설정이 소유하므로 프론트엔드 저장소의 범위 밖입니다. 다만 다음을 코드 수준에서 지켜 CSP 적용을 방해하지 않습니다.

규칙 이유
인라인 스크립트를 작성하지 않습니다 unsafe-inline 없이 동작해야 합니다
인라인 스타일 속성을 최소화합니다 Angular가 nonce 방식을 지원하므로 CSP_NONCE를 설정합니다
외부 스크립트 CDN 로드를 지양합니다 소스 허용 목록이 넓어집니다

5. 민감정보 노출

대상 규칙
URL 토큰·개인식별정보를 경로나 쿼리에 두는 것을 금지합니다. 히스토리·리퍼러·서버 로그에 남습니다
콘솔 로그 운영 빌드에 개인정보나 토큰을 출력하는 것을 금지합니다
에러 메시지 서버가 내려준 내부 정보(스택, 쿼리, 내부 식별자)를 화면에 표시하지 않습니다
번들 API 키·비밀값을 소스에 포함하지 않습니다. 번들은 공개된 것으로 취급합니다
전송 상태 정적 생성 경로의 조회 결과는 HTML에 실립니다. 해당 경로에 인증 데이터를 두지 않습니다

환경 변수에 넣어도 번들에 포함되면 공개입니다. 프론트엔드에 비밀값을 둘 수 있는 자리는 없습니다.

6. 라우트 가드

가드는 경험 장치이며 보안 장치가 아닙니다. 상세 규칙은 라우팅과 네비게이션 4절에 있습니다.

가드가 있으니 서버 검증이 느슨해도 된다는 판단을 금지합니다. 가드는 우회 가능하며, 우회한 요청은 서버에 그대로 도달합니다.

권한에 따라 UI 요소를 숨기는 것도 같습니다. 숨긴 버튼의 동작은 여전히 API 호출이며, 사용자는 그 호출을 직접 만들 수 있습니다.

7. 의존성

규칙 내용
취약점 점검 npm audit를 CI에서 실행합니다
추가 기준 유지보수 상태와 의존성 규모를 확인한 뒤 추가합니다
버전 고정 실험 단계 패키지는 패치 수준까지 고정합니다

shared/ui에 복사해 온 Spartan helm 소스는 우리 코드이므로 우리가 취약점 책임을 집니다. brain은 의존성이므로 갱신 대상입니다.

8. 금지 사항 요약

금지 사유
리프레시 토큰을 localStorage에 보관 XSS로 탈취 시 만료가 무의미해집니다
개별 요청에서 인증 헤더 직접 조립 부착 규칙이 흩어져 누락과 유출을 검출할 수 없습니다
외부 도메인 요청에 토큰 부착 서드파티로 토큰이 새어 나갑니다
bypassSecurityTrust* 사용 XSS 방어를 명시적으로 끕니다
[innerHTML]에 서버 응답 바인딩 서버가 받은 값은 사용자 입력입니다
URL에 토큰·개인식별정보 기재 히스토리와 로그에 남습니다
소스에 API 키·비밀값 포함 번들은 공개된 것입니다
가드나 UI 숨김을 인가 수단으로 신뢰 클라이언트는 조작 가능합니다
© 2026 dev.goraebap