HTTP 401과 403 에러의 결정적 차이와 올바른 사용법

API 응답으로 401을 받았는데 로그인 화면이 나오지 않거나, 403을 반환했는데 사용자가 "비밀번호가 틀렸나?"라고 혼란스러워한 경험이 있다면 이 글이 필요합니다. 401 Unauthorized와 403 Forbidden은 둘 다 접근 거부를 의미하지만, 클라이언트가 취해야 할 액션이 완전히 다릅니다. 이 차이를 정확히 이해하지 못하면 UX 혼란과 불필요한 디버깅 시간을 초래합니다.


HTTP 401과 403 에러의 결정적 차이와 올바른 사용법




401 Unauthorized: "당신이 누구인지 증명하세요"

401은 인증(Authentication) 정보가 없거나 유효하지 않을 때 반환하는 상태코드입니다. 서버는 "당신이 누구인지 모르겠으니, 신원을 증명하세요"라고 요구하는 것입니다.

  • 언제 사용: 로그인하지 않은 사용자가 보호된 리소스에 접근하거나, 토큰이 만료되었거나, 잘못된 인증 정보를 제공했을 때
  • 필수 헤더: WWW-Authenticate 헤더를 응답에 포함해야 합니다. 이 헤더는 클라이언트에게 어떤 인증 방식(Basic, Bearer 등)을 사용해야 하는지 알려줍니다.
  • 클라이언트 액션: 로그인 화면으로 리다이렉트하거나, 토큰 갱신 프로세스를 시작합니다.

실전 예시:

  • JWT 토큰 없이 /api/user/profile 요청 → 401 + WWW-Authenticate: Bearer
  • 만료된 세션으로 대시보드 접근 → 401 + 로그인 페이지로 리다이렉트

주의사항: 401의 이름이 "Unauthorized"지만, 실제로는 "Unauthenticated(인증되지 않음)"를 의미합니다. 이름과 의미가 불일치하는 대표적인 HTTP 상태코드입니다.

403 Forbidden: "당신이 누군지 알지만, 권한이 없습니다"

403은 인증은 성공했지만 권한(Authorization)이 부족할 때 반환합니다. 서버는 "당신이 누구인지는 알지만, 이 리소스에 접근할 권한이 없습니다"라고 명확히 거부하는 것입니다.

  • 언제 사용: 일반 사용자가 관리자 전용 API를 호출하거나, 다른 사용자의 개인정보를 조회하려 하거나, IP 화이트리스트에 없는 곳에서 접근할 때
  • 재인증 불필요: 401과 달리 WWW-Authenticate 헤더를 포함하지 않습니다. 다시 로그인해도 상황이 바뀌지 않기 때문입니다.
  • 클라이언트 액션: "접근 권한이 없습니다" 메시지를 표시하고, 사용자에게 권한 요청 방법을 안내합니다.

실전 예시:

  • 일반 회원이 /api/admin/users 접근 시도 → 403
  • 사용자 A가 사용자 B의 /api/user/123/orders 조회 시도 → 403
  • 내부 IP 전용 API를 외부에서 호출 → 403

보안 팁: 리소스 존재 여부를 숨기고 싶다면 403 대신 404를 반환할 수도 있습니다. 예를 들어 "비공개 게시글"에 대해 403을 주면 "게시글은 존재하지만 볼 수 없다"는 정보가 노출되므로, 404로 처리하여 존재 자체를 감출 수 있습니다.

실무에서 자주 발생하는 혼동 케이스

프로젝트에서 다음과 같은 실수를 자주 목격했습니다:

  1. 토큰 만료 시 403 반환: 토큰이 만료되면 401을 줘야 클라이언트가 토큰 갱신 로직을 실행합니다. 403을 주면 "권한 없음" 메시지만 보이고 재로그인 흐름이 작동하지 않습니다.
  2. 401에 WWW-Authenticate 헤더 누락: RFC 7235 표준에 따르면 401 응답은 반드시 이 헤더를 포함해야 합니다. 누락 시 일부 HTTP 클라이언트가 예상대로 동작하지 않을 수 있습니다.
  3. 권한 부족을 401로 처리: 로그인은 되어 있지만 역할(Role)이 부족한 경우, 401을 주면 사용자가 "로그아웃되었나?"라고 혼란스러워합니다. 이때는 명확히 403을 반환해야 합니다.

판단 기준 정리:

  • 인증 정보가 없거나 잘못됨 → 401 + WWW-Authenticate
  • 인증은 됐지만 권한 부족 → 403
  • 리소스 존재 여부를 숨기고 싶음 → 404

API 설계 시 체크리스트

다음 규칙을 팀 내 API 가이드라인으로 정리하면 일관성 있는 에러 처리가 가능합니다:

  • 401 반환 시: WWW-Authenticate 헤더 필수 포함 (예: WWW-Authenticate: Bearer realm="api")
  • 403 반환 시: 응답 바디에 구체적인 이유를 명시 (예: {"error": "ADMIN_ROLE_REQUIRED"})
  • 클라이언트 구현: 401 수신 시 자동 로그인 페이지 이동 또는 토큰 갱신, 403 수신 시 권한 안내 메시지 표시
  • 로깅: 403은 보안 이벤트일 수 있으므로, 누가(User ID), 언제, 어떤 리소스에 접근 시도했는지 로그로 남기는 것을 권장합니다.

실무 꿀팁: Postman이나 Swagger에서 API 테스트 시, 401/403 응답의 헤더와 바디를 함께 확인하는 습관을 들이십시오. 상태코드만 보고 판단하면 WWW-Authenticate 누락 같은 문제를 놓치기 쉽습니다.

핵심 요약 및 실행 과제

401은 "인증 필요"를 의미하며 WWW-Authenticate 헤더와 함께 반환해야 하고, 403은 "인증됐지만 권한 없음"을 명확히 구분하는 상태코드입니다. 두 코드를 혼용하면 클라이언트 UX가 망가지고 불필요한 고객 문의가 증가합니다.

당장 실행할 Action Item: 현재 운영 중인 API 중 인증/권한 관련 엔드포인트를 점검하고, 401 응답에 WWW-Authenticate 헤더가 누락되었거나 권한 부족 케이스에서 401을 반환하는 곳이 있는지 확인하십시오. 발견 시 즉시 403으로 수정하고, 클라이언트 에러 핸들링 로직도 함께 검토하기를 권장합니다.


#함께 읽으면 좋은 글

npm install 의존성 충돌 해결 완벽 가이드: legacy-peer-deps 옵션 실전 사용법 : 바로보기

댓글