RFC 10017 브라우저 기반 애플리케이션을 위한 OAuth 2.0 2026년 8월
Parecki 외 현행 모범 사례 [페이지]
스트림:
인터넷 엔지니어링 태스크 포스 (IETF)
RFC:
10017
BCP:
212
범주:
현행 모범 사례
발행일:
ISSN:
2070-1721
저자:
A. Parecki
Okta
P. De Ryck
Pragmatic Web Security
D. Waite
Ping Identity

RFC 10017

브라우저 기반 애플리케이션을 위한 OAuth 2.0

초록

이 명세는 OAuth 2.0을 사용하는 브라우저 기반 애플리케이션을 개발할 때 반드시 고려해야 하는 위협, 공격의 결과, 보안 고려 사항 및 모범 사례를 자세히 설명합니다.

이 메모의 상태

이 메모는 인터넷 현행 모범 사례를 문서화합니다.

이 문서는 인터넷 엔지니어링 태스크 포스 (IETF)의 산출물입니다. 이 문서는 IETF 커뮤니티의 합의를 나타냅니다. 이 문서는 공개 검토를 거쳤으며 인터넷 엔지니어링 운영 그룹 (IESG)의 발행 승인을 받았습니다. BCP에 관한 자세한 정보는 RFC 7841의 제2절에서 확인할 수 있습니다.

이 문서의 현재 상태, 정오표 및 이 문서에 대한 피드백 제공 방법에 관한 정보는 https://www.rfc-editor.org/info/rfc10017에서 확인할 수 있습니다.

▲

목차

1. 서론

이 명세는 브라우저에서 실행되는 애플리케이션에 OAuth 2.0 클라이언트를 구현하기 위한 다양한 아키텍처 패턴을 설명합니다. 또한 브라우저 기반 애플리케이션의 보안 문제를 제시하고, 각 패턴이 이러한 문제 중 일부를 해결하는 데 어떻게 도움이 되는지 분석합니다.

이 문서는 OAuth 클라이언트로 동작하는 JavaScript 프런트엔드 애플리케이션 (제 1.1절, [RFC6749]에 정의됨)이 인가 서버(제1.1절, [RFC6749])와 상호작용하여 액세스 토큰과 선택적으로 갱신 토큰을 얻는 방식에 중점을 둡니다. 클라이언트는 액세스 토큰을 사용하여 리소스 서버의 보호된 리소스(제1.1절, [RFC6749])에 액세스합니다. OAuth를 사용할 때 클라이언트, 인가 서버 및 리소스 서버는 각각을 동일한 주체가 소유하거나 운영하는지와 관계없이 모두 독립된 당사자로 간주됩니다.

많은 웹 애플리케이션은 공통 도메인에서 실행되는 프런트엔드와 API로 구성되므로 OAuth 2.0에 의존하지 않는 아키텍처를 사용할 수 있다는 점에 유의하십시오. 이에 관한 자세한 내용은 제7.1절에서 설명합니다. 이러한 시나리오에서는 연합 사용자 인증에 OpenID Connect [OpenID]를 사용할 수 있으며, 이후 애플리케이션이 사용자의 인증 상태를 유지합니다. 이러한 시나리오 (OpenID Connect의 기반 명세로서 OAuth 2.0만 사용함)는 이 명세의 범위에 포함되지 않습니다.

OAuth 2.0과 OpenID Connect를 사용하는 네이티브 애플리케이션 개발자를 위해 이러한 기술의 통합을 안내하는 IETF BCP(현행 모범 사례)가 발행되었습니다. 이 문서는 공식적으로 [RFC8252] 또는 BCP 212로 알려져 있지만, 개발자가 이러한 사례를 도입하도록 지원하는 OpenID Foundation 후원 라이브러리 모음의 이름을 따라 흔히 "AppAuth"라고 합니다. [RFC8252]에서는 필요한 경우 추가 OAuth 확장을 통합하는 것을 포함하여 네이티브 애플리케이션에서 OAuth 클라이언트를 안전하게 구현하는 방법에 관한 구체적인 권고 사항을 제시합니다.

이 명세인 "브라우저 기반 애플리케이션을 위한 OAuth 2.0"에서는 브라우저 기반 애플리케이션의 보안 속성이 네이티브 애플리케이션의 보안 속성과 크게 다르다는 점을 강조하고, OAuth 클라이언트를 네이티브 애플리케이션과 브라우저 기반 애플리케이션으로 구현할 때의 유사점을 다룹니다. 이 문서는 OpenID Connect에서 추가 고려 사항을 제공하는 경우를 제외하면 주로 OAuth에 중점을 둡니다.

이러한 권고 사항 중 다수는 OAuth 2.0 보안을 위한 현행 모범 사례 [RFC9700]에서 파생되었으며, 브라우저 기반 애플리케이션도 이러한 권고 사항을 따라야 합니다. 이 문서는 [RFC9700]에서 제시한 여러 권고 사항을 확장하고 더욱 제한합니다.

2. 표기 규칙

이 문서에서 핵심어 "반드시 해야 한다", "절대로 해서는 안 된다", "필수이다", "해야 한다", "해서는 안 된다", "해야 한다", "해서는 안 된다", "권고된다", "권고되지 않는다", "할 수 있다" 및 "선택 사항이다"는 원문에서 여기에 표시된 것처럼 모두 대문자로 나타나는 경우에만 BCP 14 [RFC2119] [RFC8174]에 설명된 대로 해석해야 합니다.

3. 용어

이 명세에서는 OAuth 2.0 [RFC6749]에서 정의한 "액세스 토큰", "인가 엔드포인트", "인가 권한 부여", "인가 서버", "클라이언트", "클라이언트 식별자"(클라이언트 ID), "보호된 리소스", "갱신 토큰", "리소스 서버" 및 "토큰 엔드포인트"라는 용어를 사용합니다. 또한 [RFC6750]에서 정의한 "베어러 토큰"을 사용합니다.

참조된 명세에서 정의한 용어 외에도 이 문서에서는 다음 용어를 사용합니다.

OAuth:

이 문서에서 "OAuth"는 "OAuth 2.0" [RFC6749] [RFC6750]을 의미합니다.

브라우저 기반 애플리케이션:

웹 브라우저에서 동적으로 다운로드되어 실행되는 애플리케이션으로, 일반적으로 JavaScript로 작성됩니다. "단일 페이지 애플리케이션" 또는 "SPA"라고도 합니다.

이 문서는 브라우저가 런타임 환경에서 실행하는 브라우저 기반 애플리케이션의 보안을 다룹니다. 대부분의 시나리오에서 이러한 애플리케이션은 JavaScript 실행 환경에서 실행되는 JavaScript 애플리케이션입니다. 이 시나리오가 널리 사용되므로 이 문서에서는 "JavaScript"라는 용어를 브라우저에서 애플리케이션의 런타임 내에 코드가 실행될 수 있도록 하는 모든 메커니즘을 지칭하는 데 사용합니다. 이 문서의 권고 사항과 고려 사항은 JavaScript 언어나 그 런타임에만 관련되는 것이 아니라 WebAssembly(WASM) [W3C.wasm-core-2]와 같은 브라우저의 다른 언어와 런타임 환경에도 적용됩니다.

PKCE:

코드 교환을 위한 증명 키 [RFC7636]. OAuth 인가 코드에 대한 다양한 공격을 방지하는 메커니즘입니다.

DPoP:

소유 증명 제시 [RFC9449]. 액세스 토큰을 발급받은 클라이언트만 사용할 수 있도록 제한하는 OAuth 2.0 메커니즘입니다.

CORS:

교차 오리진 리소스 공유 [Fetch]. 브라우저의 동일 오리진 정책에 예외를 적용할 수 있도록 하는 메커니즘입니다.

CSP:

콘텐츠 보안 정책 [W3C.CSP3]. 특정 웹 페이지가 가져오거나 실행할 수 있는 리소스를 제한하는 메커니즘입니다.

4. 브라우저 기반 애플리케이션에서의 OAuth 2.0 역사

OAuth 2.0이 처음 [RFC6749]와 [RFC6750]에 명시되었을 당시 브라우저 기반 JavaScript 애플리케이션에는 동일 오리진 정책을 엄격하게 준수하는 해결책이 필요했습니다. 일반적인 OAuth 2.0 배포에서는 애플리케이션이 인가 서버와 다른 도메인에서 실행되었으므로, 교차 오리진 POST 요청이 필요한 인가 코드 권한 부여 유형(제4.1절, [RFC6749])을 과거에는 사용할 수 없었습니다. 이러한 제한은 URL의 프래그먼트 부분을 통해 프런트 채널로 액세스 토큰을 반환하여 교차 오리진 POST 요청의 필요성을 우회하는 암시적 권한 부여(제4.2절, [RFC6749])를 정의한 동기 중 하나였습니다.

그러나 암시적 권한 부여에는 일반적으로 URL에 액세스 토큰이 노출되는 것과 관련된 취약점 등 여러 단점이 있습니다. 이러한 공격에 관한 분석과 브라우저에서 암시적 권한 부여를 사용할 때의 단점은 제7.2절을 참조하십시오. 추가적인 공격과 보안 고려 사항은 [RFC9700]에서 확인할 수 있습니다.

현대 웹 개발에서는 교차 오리진 리소스 공유(CORS)가 널리 도입되어 [Fetch](동일 오리진 정책에 예외를 적용할 수 있도록 함) 브라우저 기반 애플리케이션에서 OAuth 2.0 인가 코드 흐름을 사용하고 토큰 엔드포인트에서 인가 코드를 액세스 토큰으로 교환하는 POST 요청을 보낼 수 있습니다. 인가 코드 권한 부여 유형에서는 갱신 토큰을 사용할 수 있으므로 보안 저장소에 대한 액세스가 제한되거나 전혀 없는 공개 클라이언트(제2.1절, [RFC6749]에 정의됨)임에도 이러한 동작이 브라우저 기반 클라이언트에도 도입되었습니다. 또한 코드 교환을 위한 증명 키(PKCE) [RFC7636]를 흐름에 추가하면 인가 코드 삽입을 방지할 뿐만 아니라 인가 코드가 가로채이더라도 공격자가 이를 사용할 수 없도록 보장합니다.

이러한 이유와 그 밖의 경험에서 얻은 교훈에 따라 현재 브라우저 기반 애플리케이션의 모범 사례는 PKCE와 함께 OAuth 2.0 인가 코드 권한 부여 유형을 사용하는 것입니다. 브라우저 기반 애플리케이션을 배포하는 아키텍처 패턴에는 그에 대응하는 서버 측 구성 요소가 있는 패턴과 없는 패턴이 모두 있습니다. 각 아키텍처에는 이 문서에서 더 자세히 다루는 고유한 장단점과 고려 사항이 있습니다. 자사 공통 도메인 애플리케이션에는 추가 고려 사항이 적용됩니다.

5. 악성 JavaScript의 위협

악성 JavaScript는 브라우저 기반 애플리케이션에 중대한 위험을 초래합니다. 교차 사이트 스크립팅(XSS)이나 원격 코드 파일 침해 등의 공격 벡터는 공격자에게 애플리케이션의 실행 컨텍스트에서 임의의 코드를 실행할 수 있는 능력을 제공합니다. 이 악성 코드는 어떠한 방식으로도 주 애플리케이션 코드와 격리되지 않습니다. 따라서 악성 코드는 실행 중인 실행 컨텍스트를 제어할 뿐만 아니라 애플리케이션의 오리진 내에서 작업을 수행할 수도 있습니다. 구체적으로 악성 코드는 현재 페이지에서 데이터를 탈취하고, 다른 동일 오리진 브라우징 컨텍스트와 상호작용하며, 애플리케이션의 오리진 내에서 백엔드로 요청을 보내고, 오리진 기반 저장 메커니즘(예: localStorage, IndexedDB)에서 데이터를 탈취하는 등의 작업을 할 수 있습니다.

무엇보다도 먼저 공격자가 발판을 확보하지 못하도록 선제적 조치를 취하는 것이 중요합니다. 여기에는 다음 조치 등이 포함됩니다.

추가 권고 사항은 Open Worldwide Application Security Project (OWASP) 치트 시트 모음 [OWASPCheatSheet]에서 확인할 수 있습니다.

안타깝게도 이러한 보안 지침을 적용하더라도 공격자가 악성 JavaScript의 실행을 유발할 방법을 찾을 위험이 있다는 사실이 과거 사례를 통해 드러났습니다. 악성 JavaScript가 존재하는 상황에서 브라우저 기반 애플리케이션의 보안을 분석할 때는 악성 JavaScript 코드가 적법한 애플리케이션 코드와 동일한 권한을 갖는다는 점을 반드시 인식해야 합니다. 모든 JavaScript 애플리케이션은 어느 정도 이러한 위험에 노출됩니다.

애플리케이션은 작동하는 데 필요한 인가를 부여하는 OAuth 토큰을 얻을 수 있습니다. 이와 결합되면 침해된 코드가 해당 인가를 악의적인 목적으로 사용할 수 있게 됩니다. 공격자가 인가를 악용할 위험은 피할 수 없지만, 침해된 애플리케이션이 해당 인가를 악용할 수 있는 범위를 제한하는 방법은 있습니다. 예를 들어 얻을 수 있는 토큰 유형을 제한하거나 토큰을 브라우저에 바인딩하여 이러한 액세스를 애플리케이션이 사용 중인 시간으로 제한할 수 있습니다.

적법한 애플리케이션 코드가 변수에 액세스하거나 함수를 호출할 수 있다면 악성 JavaScript 코드도 정확히 동일한 작업을 할 수 있습니다. 또한 악성 JavaScript 코드는 애플리케이션의 일반적인 실행 흐름은 물론 애플리케이션 수준의 모든 방어 수단도 변조할 수 있습니다. 이러한 방어 수단은 일반적으로 애플리케이션 내부에서 제어되기 때문입니다. 예를 들어 공격자는 이벤트 리스너를 제거하거나 재정의하고, 내장 함수의 동작을 수정하며(프로토타입 오염), 프레임 내 페이지의 로드를 중지할 수 있습니다.

악성 JavaScript가 브라우저 기반 애플리케이션에 미치는 영향은 광범위하게 연구되어 잘 알려진 주제입니다. 그러나 OAuth 클라이언트로 동작하는 브라우저 기반 애플리케이션에 악성 JavaScript가 미치는 구체적인 영향은 매우 독특합니다. 이제 악성 JavaScript가 OAuth 흐름 중의 상호작용에 영향을 줄 수 있기 때문입니다. 이 절에서는 OAuth 클라이언트의 책임을 가진 브라우저 기반 애플리케이션에 악성 JavaScript 코드가 초래하는 위협을 살펴봅니다. 제5.1절에서는 공격자가 악성 JavaScript 코드를 실행할 방법을 찾은 후 사용할 수 있는 몇 가지 시나리오를 설명합니다. 이러한 시나리오는 단순한 토큰 외부 유출을 훨씬 뛰어넘는 공격자의 실제 역량을 명확히 보여 줍니다. 제5.2절에서는 이러한 공격 시나리오가 OAuth 클라이언트에 미치는 영향을 분석합니다.

이 명세의 나머지 부분에서는 여러 아키텍처 패턴의 보안 속성을 분석하기 위해 이러한 공격 시나리오와 결과를 다시 참조합니다.

5.1. 공격 시나리오

이 절에서는 공격자가 악성 JavaScript 코드를 실행할 수 있는 취약점을 발견한 후 수행할 수 있는 몇 가지 공격 시나리오를 제시합니다. 공격 시나리오에는 단순한 시나리오(제5.1.1절)와 정교한 시나리오(제5.1.3절)가 포함됩니다. 이 열거는 모든 경우를 망라하지 않고 OAuth 관련 기능으로 범위를 좁혔으며 특별한 순서 없이 제시되었다는 점에 유의하십시오.

5.1.1. 단일 실행 토큰 탈취

단일 실행 토큰 탈취 시나리오는 공격자가 클라이언트의 현재 토큰을 획득하여 외부로 유출하는 단순한 토큰 외부 유출 공격을 다룹니다. 이 시나리오는 다음 단계로 구성됩니다.

  • 악성 JavaScript 코드를 실행합니다.

  • 애플리케이션에서 선호하는 저장 메커니즘(제8절)으로부터 토큰을 얻습니다.

  • 공격자가 제어하는 서버로 토큰을 보냅니다.

  • 탈취한 토큰을 저장하거나 악용합니다.

침해된 액세스 토큰과 관련된 위험을 줄이기 위해 권고되는 방어 전략은 토큰의 범위와 수명을 줄이는 것입니다. 갱신 토큰의 경우 갱신 토큰 순환(제 4.14.2절, [RFC9700]에 정의됨)은 탐지 및 수정 메커니즘을 제공합니다. 송신자 제한 토큰(제9.2절)은 탈취된 액세스 토큰에 대한 추가 보호 계층을 제공합니다.

이 공격 시나리오는 단순하며 악성 JavaScript의 위험을 설명하는 데 자주 사용된다는 점에 유의하십시오. 브라우저 기반 애플리케이션의 보안을 논의할 때는 공격자의 역량을 이 시나리오에서 설명한 공격으로 제한해서는 안 됩니다.

5.1.2. 지속적인 토큰 탈취

지속적인 토큰 탈취 공격 시나리오는 단일 실행 토큰 탈취 시나리오(제5.1.1절)의 더 발전된 변형입니다. 공격자는 악성 코드가 실행되는 즉시 토큰을 탈취하는 대신 애플리케이션의 토큰을 지속해서 탈취하는 데 필요한 핸들러를 설정합니다. 이 시나리오는 다음 단계로 구성됩니다.

  • 악성 JavaScript 코드를 실행합니다.

  • 지속적인 토큰 탈취 메커니즘을 설정합니다(예: 10초 간격).

    • 애플리케이션에서 선호하는 저장 메커니즘(제8절)으로부터 토큰을 얻습니다.

    • 공격자가 제어하는 서버로 토큰을 보냅니다.

    • 토큰을 저장합니다.

  • 탈취한 토큰의 최신 버전을 악용할 적절한 순간까지 기다립니다.

이 시나리오의 중요한 차이점은 공격자가 항상 애플리케이션에서 사용하는 최신 토큰에 액세스할 수 있다는 것입니다. 공격 시나리오의 이러한 사소한 변화만으로도 짧은 수명이나 갱신 토큰 순환과 같은 일반적인 토큰 탈취 방어 수단을 무력화하기에 충분합니다.

액세스 토큰의 경우 공격자는 사용자의 브라우저가 온라인 상태인 동안 최신 액세스 토큰을 계속 얻을 수 있습니다. 갱신 토큰 순환만으로는 갱신 토큰의 악용을 방지하기에 충분하지 않습니다. 공격자는 애플리케이션이 최신 갱신 토큰을 사용하지 못하도록 쉽게 보장할 수 있습니다. 예를 들어 공격자는 토큰을 탈취한 후 애플리케이션의 토큰을 지우거나 사용자가 애플리케이션을 닫을 때까지 기다리거나 사용자의 브라우저가 오프라인 상태가 될 때까지 기다릴 수 있습니다. 애플리케이션이 최신 갱신 토큰을 사용하지 않으므로 감지할 수 있는 갱신 토큰 재사용이 발생하지 않으며, 공격자는 탈취한 갱신 토큰을 완전히 제어하게 됩니다.

5.1.3. 새로운 토큰의 획득 및 추출

새로운 토큰을 획득하고 추출하는 고급 공격 시나리오에서 공격자는 애플리케이션이 이미 얻은 모든 토큰을 완전히 무시합니다. 대신 공격자는 애플리케이션의 오리진과 연결된 악성 코드를 실행할 수 있는 능력을 이용합니다. 이 능력을 사용하면 공격자는 숨겨진 iframe을 삽입하고 자동 인가 코드 흐름을 시작할 수 있습니다. 이 자동 흐름은 인가 서버와 설정된 사용자의 기존 세션을 재사용하여 새롭고 독립적인 액세스 토큰(및 선택적으로 갱신 토큰)을 발급합니다. 이 시나리오는 다음 단계로 구성됩니다.

  • 악성 JavaScript 코드를 실행합니다.

  • iframe에서 인가 코드를 얻기 위한 핸들러를 설정합니다(예: 프레임의 URL을 모니터링하거나 웹 메시징 [WebMessaging] 사용).

  • 페이지에 숨겨진 iframe을 삽입하고 인가 요청으로 초기화합니다. iframe의 인가 요청은 사용자 세션 내에서 이루어지며 세션이 여전히 활성 상태라면 인가 코드가 발급됩니다. 이 단계는 이 시나리오의 마지막 단락에서 설명한 것처럼 인가 서버에서 프레임 기반 자동 흐름을 지원해야 한다는 점에 유의하십시오.

  • 앞서 설치한 핸들러를 사용하여 iframe에서 인가 코드를 추출합니다.

  • 공격자가 제어하는 서버로 인가 코드를 보냅니다.

  • 인가 코드를 새로운 토큰 집합으로 교환합니다.

  • 탈취한 토큰을 악용합니다.

이 시나리오에서 가장 중요한 점은 기존 토큰의 탈취에 집중하는 대신 새로운 OAuth 흐름을 실행한다는 것입니다. 본질적으로 애플리케이션이 저장된 토큰을 공격자로부터 완전히 격리할 수 있는 토큰 저장 메커니즘을 찾아내더라도 공격자는 여전히 새로운 토큰 집합을 요청할 수 있습니다. 공격자가 브라우저에서 애플리케이션을 제어하므로 공격자의 인가 코드 흐름을 적법한 인가 코드 흐름과 구별할 수 없다는 점에 유의하십시오.

공개 브라우저 기반 OAuth 클라이언트의 보안이 리디렉션 URI와 애플리케이션의 오리진에 전적으로 의존하기 때문에 이 공격 시나리오가 가능합니다. 공격자가 애플리케이션의 오리진에서 악성 JavaScript 코드를 실행하면 동일 오리진 프레임을 검사할 수 있는 능력을 얻게 됩니다. 따라서 주 실행 컨텍스트에서 실행되는 공격자의 코드는 동일 오리진 프레임에 로드된 리디렉션 URI를 검사하여 인가 코드를 추출할 수 있습니다.

프런트엔드 애플리케이션에서 이 공격 시나리오에 대응할 수 있는 실용적인 보안 메커니즘은 없습니다. 공격자에게 새롭고 독립적인 토큰 집합이 있으므로 짧은 액세스 토큰 수명과 갱신 토큰 순환은 효과가 없습니다. DPoP [RFC9449]와 같은 고급 보안 메커니즘도 마찬가지로 효과가 없습니다. 공격자가 자체 키 쌍을 사용하여 새로 얻은 토큰에 DPoP를 설정하고 사용할 수 있기 때문입니다. 모든 인가 코드 흐름에 사용자 상호작용을 요구하면 새로운 토큰의 자동 발급을 효과적으로 중지할 수 있지만, 이는 첫 페이지 로드 시 애플리케이션을 초기화하거나 관련된 여러 애플리케이션 사이에서 싱글 사인온을 수행하는 것처럼 널리 확립된 패턴에 상당한 영향을 미치므로 실용적인 조치가 아닙니다.

5.1.4. 사용자 브라우저를 통한 요청 프록시 처리

이 공격 시나리오에서는 공격자가 사용자의 브라우저에서 실행 중인 OAuth 클라이언트 애플리케이션 내부로부터 OAuth 리소스 서버로 직접 요청을 보냅니다. 이 시나리오에서는 브라우저가 자체 쿠키나 토큰을 요청에 포함하므로 공격자가 토큰을 얻기 위해 애플리케이션을 악용할 필요가 없습니다. 공격자가 리소스 서버로 보낸 요청은 공격자가 적법한 애플리케이션과 동일한 컨텍스트에서 코드를 실행하므로 적법한 애플리케이션이 보낸 요청과 구별할 수 없습니다. 이 시나리오는 다음 단계로 구성됩니다.

  • 악성 JavaScript 코드를 실행합니다.

  • 리소스 서버로 요청을 보내고 응답을 처리합니다.

공격자는 리소스 서버로 보내는 요청을 인가하기 위해 단순히 클라이언트 애플리케이션의 동작을 모방합니다. 예를 들어 클라이언트 애플리케이션이 나가는 요청에 프로그래밍 방식으로 액세스 토큰을 첨부하면 공격자도 동일하게 수행합니다. 클라이언트 애플리케이션이 외부 구성 요소를 사용하여 적절한 액세스 토큰을 요청에 추가한다면 이 외부 구성 요소는 공격자의 요청에도 토큰을 추가합니다.

이 공격 패턴은 잘 알려져 있으며 HttpOnly 세션 쿠키를 사용하는 서버 측 웹 애플리케이션에서도 발생합니다. 일반적으로 이 시나리오는 애플리케이션 수준의 보안 조치로 중지하거나 방지할 수 없다고 인정됩니다. 예를 들어 DPoP [RFC9449]는 이 공격 시나리오를 명시적으로 범위 밖으로 간주합니다.

5.2. 공격 결과

공격 시나리오가 성공적으로 실행되면 액세스 토큰과 갱신 토큰이 탈취되거나 사용자의 브라우저에서 실행 중인 클라이언트 애플리케이션이 하이재킹될 수 있습니다. 이러한 결과는 각각 브라우저 기반 OAuth 클라이언트와 관련이 있습니다. 아래에서는 심각도가 높은 순서대로 설명합니다.

5.2.1. 탈취된 갱신 토큰 악용

공격자가 브라우저 기반 OAuth 클라이언트로부터 유효한 갱신 토큰을 얻으면 인가 서버에서 갱신 토큰 권한 부여를 실행하여 갱신 토큰을 악용할 수 있습니다. 갱신 토큰 권한 부여의 응답에는 액세스 토큰이 포함되며, 이로 인해 공격자는 보호된 리소스에 액세스할 수 있습니다(제5.2.2절 참조). 본질적으로 탈취한 갱신 토큰을 악용하면 리소스 서버로 보내는 요청에서 적법한 클라이언트 애플리케이션을 장기간 사칭할 수 있습니다.

인가 서버가 만료되거나 순환된 갱신 토큰을 거부하거나 갱신 토큰이 폐기된 경우에만 공격이 중지됩니다. 일반적인 브라우저 기반 OAuth 클라이언트에서는 갱신 토큰이 여러 시간 또는 며칠 동안 유효하게 유지되는 경우가 드물지 않습니다.

5.2.2. 탈취된 액세스 토큰 악용

공격자가 유효한 액세스 토큰을 얻으면 리소스 서버로 보내는 요청에서 적법한 클라이언트 애플리케이션을 사칭할 수 있습니다. 구체적으로 액세스 토큰을 소유한 공격자는 해당 유효한 액세스 토큰을 허용하는 모든 리소스 서버로 임의의 요청을 보낼 수 있습니다. 본질적으로 탈취한 액세스 토큰을 악용하면 리소스 서버에 대해 적법한 클라이언트 애플리케이션을 단기간 사칭할 수 있습니다.

액세스 토큰이 만료되거나 인가 서버에서 토큰이 폐기되면 공격이 종료됩니다. 일반적인 브라우저 기반 OAuth 클라이언트에서 액세스 토큰 수명은 몇 분에서 몇 시간까지로 매우 짧을 수 있습니다.

액세스 토큰을 소유하면 공격자가 이를 제한 없이 사용할 수 있다는 점에 유의하십시오. 공격자는 모든 HTTP 메서드, 대상 URL, 헤더 값 또는 본문을 사용하여 리소스 서버로 임의의 요청을 보낼 수 있습니다.

애플리케이션은 DPoP를 사용하여 액세스 토큰을 브라우저가 보유한 내보낼 수 없는 키에 바인딩할 수 있습니다. 이 경우 공격자가 탈취한 액세스 토큰을 악용하기가 훨씬 어려워집니다. 구체적으로 DPoP를 사용하면 사용자의 브라우저에서 증명을 계산하는 온라인 공격을 수행해야만 공격자가 탈취한 애플리케이션 토큰을 악용할 수 있습니다. 이 공격은 제 11.4절, [RFC9449]에 자세히 설명되어 있습니다. 그러나 제5.1.3절에서 설명한 것처럼 공격자가 새로운 액세스 토큰(및 선택적으로 갱신 토큰)을 얻으면 공격자가 제어하는 키 쌍을 사용하여 이러한 토큰에 DPoP를 설정할 수 있습니다. 이 경우 공격자는 새로 얻은 액세스 토큰을 다시 제한 없이 악용할 수 있습니다.

5.2.3. 클라이언트 하이재킹

토큰을 탈취할 수 없거나 탈취할 필요가 없는 경우 공격자는 사용자의 브라우저에서 실행 중인 OAuth 클라이언트 애플리케이션을 하이재킹할 수도 있습니다. 이렇게 하면 공격자는 적법한 클라이언트 애플리케이션이 수행할 수 있는 모든 작업을 수행할 수 있습니다. 예를 들면 페이지의 데이터를 검사하거나 페이지를 수정하고 백엔드 시스템으로 요청을 보내는 작업이 있습니다. 또는 공격자는 애플리케이션에 대한 액세스를 악용하여 세션 고정과 같은 공격으로 클라이언트가 공격자를 대신하여 동작하도록 속이는 등의 추가 공격을 시작할 수도 있습니다. [SessionFixation].

클라이언트 하이재킹은 탈취된 사용자 토큰을 직접 악용하는 것보다 영향력이 약하다는 점에 유의하십시오. 클라이언트 하이재킹 시나리오에서 공격자는 토큰을 직접 제어할 수 없으며 클라이언트 애플리케이션에 적용되는 보안 정책의 제한을 받습니다. 예를 들어 admin.example.org에서 실행되는 리소스 서버는 web.example.org에서 실행되는 클라이언트의 요청을 거부하는 CORS 정책으로 구성할 수 있습니다. 클라이언트에서 사용하는 액세스 토큰을 리소스 서버가 허용하더라도 리소스 서버의 엄격한 CORS 구성은 그러한 요청을 허용하지 않습니다. 이러한 엄격한 CORS 정책이 없는 리소스 서버는 여전히 침해된 클라이언트 애플리케이션에서 오는 악의적인 요청의 대상이 될 수 있습니다.

6. 애플리케이션 아키텍처 패턴

보호된 리소스에 액세스하기 위해 OAuth에 의존하는 브라우저 기반 애플리케이션을 구축할 때 사용할 수 있는 주요 아키텍처 패턴은 세 가지입니다.

각 아키텍처 패턴은 보안과 단순성 사이에서 서로 다른 절충점을 제공합니다. 이 절의 패턴은 보안 수준이 높은 순서대로 제시됩니다.

6.1. 프런트엔드용 백엔드 (BFF)

이 절에서는 모든 OAuth 관련 작업과 API 상호작용을 처리하기 위해 백엔드 구성 요소에 의존하는 브라우저 기반 애플리케이션의 아키텍처를 설명합니다. BFF에는 세 가지 핵심 책임이 있습니다.

  1. BFF는 기밀 OAuth 클라이언트(제 2.1절, [RFC6749]에 정의됨)로서 인가 서버와 상호작용합니다.

  2. BFF는 쿠키 기반 세션의 컨텍스트에서 OAuth 액세스 토큰과 갱신 토큰을 관리하여 브라우저 기반 애플리케이션에 토큰이 직접 노출되지 않도록 합니다.

  3. BFF는 모든 요청을 리소스 서버로 전달하며, 리소스 서버로 전달하기 전에 올바른 액세스 토큰을 요청에 추가합니다.

이 아키텍처에서 BFF는 서버 측 구성 요소로 실행되지만 프런트엔드 애플리케이션의 구성 요소입니다. API 게이트웨이나 역방향 프록시와 같은 다른 아키텍처 개념과 혼동하지 않으려면 BFF가 프런트엔드 애플리케이션의 OAuth 클라이언트가 된다는 점을 기억해야 합니다.

공격자가 브라우저 기반 애플리케이션 내부에서 악성 코드를 실행할 수 있더라도 이 애플리케이션 아키텍처는 앞서 설명한 공격 시나리오 대부분을 견딜 수 있습니다. 토큰은 BFF에서만 사용할 수 있으므로 브라우저에서 추출할 수 있는 토큰이 없습니다("단일 실행 토큰 탈취"(제5.1.1절) 및 "지속적인 토큰 탈취"(제5.1.2절) 참조). BFF는 기밀 클라이언트이므로 공격자가 브라우저 내에서 새로운 흐름을 실행할 수 없습니다 ("새로운 토큰의 획득 및 추출"(제5.1.3절) 참조). 악성 브라우저 기반 코드는 여전히 애플리케이션의 오리진 내에서 실행되므로 공격자는 사용자의 브라우저 내부에서 BFF로 요청을 보낼 수 있습니다("사용자 브라우저를 통한 요청 프록시 처리"(제5.1.4절) 참조). HttpOnly 쿠키를 사용하면 공격자가 세션 상태에 직접 액세스하지 못하므로 클라이언트 하이재킹이 세션 하이재킹으로 확대되는 것을 방지할 수 있습니다.

6.1.1. 애플리케이션 아키텍처

인가 토큰 리소스 엔드포인트 엔드포인트 서버 (F) (K) 프런트엔드 용 백엔드 (BFF) (D) (B,I) (C) (E) (G) (J) (L) (A,H) 정적 웹 호스트 브라우저
그림 1: OAuth 2.0 BFF 패턴

이 아키텍처에서는 브라우저 코드(일반적으로 JavaScript)가 먼저 정적 웹 호스트에서 브라우저로 로드되고(A), 이후 애플리케이션이 브라우저에서 실행됩니다. 애플리케이션은 "check session" API 엔드포인트를 호출하여 활성 세션이 있는지 BFF에 확인합니다(B). 활성 세션이 발견되면 애플리케이션은 인증된 상태를 재개하고 (J)로 건너뜁니다.

활성 세션을 찾지 못하면 브라우저 기반 애플리케이션은 PKCE 확장을 사용하는 인가 코드 흐름을 시작하기 위해 BFF로 이동합니다(C). 이에 관한 내용은 제 6.1.3.1절에서 설명하며, BFF는 브라우저를 인가 엔드포인트로 리디렉션하여 응답합니다(D). 사용자가 다시 리디렉션되면 브라우저는 인가 코드를 BFF로 전달하고(E), BFF는 클라이언트 자격 증명과 PKCE 코드 검증기를 사용하여 토큰 엔드포인트에서 인가 코드를 토큰으로 교환할 수 있습니다(F).

BFF는 얻은 토큰을 사용자 세션과 연결하고(제6.1.2.3절 참조) 이 세션을 추적하기 위해 응답에 쿠키를 설정합니다(G). 이 시점에 리디렉션 기반 인가 코드 흐름이 완료되므로 BFF는 제어권을 프런트엔드 애플리케이션으로 되돌릴 수 있습니다. BFF는 응답에 리디렉션을 포함하여 이를 수행하며(G), 이에 따라 브라우저가 서버에서 프런트엔드를 가져옵니다(H). (H) 단계는 (A) 단계와 동일하므로 요청한 리소스가 브라우저 캐시에서 로드될 가능성이 높다는 점에 유의하십시오. 프런트엔드가 로드되면 기존 세션이 있는지 BFF에 확인하여(I) 애플리케이션이 인증된 상태를 재개할 수 있도록 합니다.

브라우저의 애플리케이션이 리소스 서버로 요청을 보내려 할 때는 BFF의 해당 엔드포인트로 요청을 보냅니다(J). 이 요청에는 (G)에서 설정한 쿠키가 포함되므로 BFF가 이 사용자 세션에 적합한 토큰을 얻을 수 있습니다. BFF는 요청에서 쿠키를 제거하고 사용자의 액세스 토큰을 요청에 첨부한 다음 실제 리소스 서버로 전달합니다(K). 이후 BFF는 응답을 브라우저 기반 애플리케이션으로 다시 전달합니다(L).

6.1.2. 구현 세부 사항

6.1.2.1. 세션 및 OAuth 엔드포인트

BFF는 브라우저 기반 애플리케이션과 BFF 사이의 상호작용을 구현하는 데 중요한 엔드포인트 집합을 제공합니다. 이 절에서는 엔드포인트의 목적과 사용 사례를 명확히 설명하기 위해 이를 자세히 다룹니다.

"check session" 엔드포인트(그림 1의 (B)와 (I))는 브라우저 기반 애플리케이션이 호출하는 API 엔드포인트입니다. 사용 가능한 경우 요청에는 세션 정보가 포함되므로 BFF가 활성 세션을 확인할 수 있습니다. 응답은 브라우저 기반 애플리케이션에 세션이 활성 상태인지 알려야 합니다. 또한 BFF는 인증된 사용자의 신원 정보와 같은 다른 정보도 포함할 수 있습니다.

인가 코드 흐름을 시작하는 엔드포인트 (C)는 브라우저가 탐색을 통해 접속합니다. 애플리케이션이 세션을 확인한 후 인증되지 않은 상태를 감지하면(B) 브라우저를 이 엔드포인트로 이동시킬 수 있습니다. 그러면 BFF가 브라우저를 인가 서버로 이동시키는 리디렉션으로 응답할 수 있습니다. 이 흐름을 시작하는 엔드포인트는 일반적으로 웹 서버에서 실행되는 기밀 클라이언트용 OAuth 2.0을 지원하는 라이브러리에 "login" 엔드포인트로 포함됩니다. BFF가 활성 세션이 없음을 감지했을 때 (B)에서 인가 코드 흐름을 시작할 수도 있다는 점에 유의하십시오. 이 경우 BFF는 응답에 인가 URI를 반환하고 애플리케이션이 이 URI로 탐색 이벤트를 트리거할 것으로 예상합니다. 그러나 이 시나리오에는 사용자 정의 구현이 필요하며 표준 OAuth 라이브러리를 사용하기가 더 어려워집니다.

인가 코드를 받는 엔드포인트(E)는 브라우저 내부의 탐색 이벤트를 통해 호출됩니다. 이 시점에는 애플리케이션이 로드되지 않았으므로 리디렉션을 처리할 수 없습니다. 흐름의 시작과 마찬가지로 리디렉션을 처리하는 엔드포인트도 표준 OAuth 라이브러리에서 제공합니다. BFF는 브라우저가 애플리케이션을 로드하도록 하는 리디렉션으로 이 요청에 응답할 수 있습니다.

마지막으로 BFF는 그림 1에 표시되지 않은 "logout" 엔드포인트도 애플리케이션에 제공할 수 있습니다. 로그아웃 엔드포인트의 정확한 동작은 애플리케이션 요구 사항에 따라 달라집니다. 표준 OAuth 라이브러리에서도 일반적으로 "logout" 엔드포인트의 구현을 제공한다는 점에 유의하십시오.

6.1.2.2. 갱신 토큰

제4.14절, [RFC9700]에서 설명한 대로 갱신 토큰을 사용할 때 BFF는 갱신 토큰을 얻고(F) 이를 사용자 세션과 연결합니다.

BFF가 사용자의 액세스 토큰이 만료되었음을 확인하고 갱신 토큰을 보유하고 있다면, 갱신 토큰을 사용하여 새 액세스 토큰을 얻을 수 있습니다. BFF OAuth 클라이언트는 기밀 클라이언트이므로 갱신 토큰 요청에 클라이언트 인증을 사용합니다. 일반적으로 BFF는 프런트엔드의 API 호출을 처리할 때 이러한 단계를 인라인으로 수행합니다. 이 경우 그림 1에 명시적으로 표시되지 않은 이러한 단계는 (J)와 (K) 사이에서 이루어집니다. 모든 토큰 정보를 서버 측에서 사용할 수 있도록 유지하는 BFF는 API 요청의 성능을 높이기 위해 토큰 만료 이벤트를 감지했을 때 새 액세스 토큰을 요청할 수도 있습니다.

갱신 토큰이 만료되면 완전히 새로운 인가 코드 흐름을 실행하지 않고서는 유효한 액세스 토큰을 얻을 방법이 없습니다. 따라서 BFF가 관리하는 쿠키 기반 세션의 수명을 갱신 토큰의 최대 수명과 동일하게 구성하는 것이 합리적입니다. 또한 BFF가 활성 세션의 갱신 토큰이 더 이상 유효하지 않다는 사실을 알게 되면 세션도 무효화하는 것이 합리적입니다.

BFF는 사용자의 토큰에 액세스하는 데 사용되는 사용자 세션을 추적하기 위해 브라우저 쿠키 [RFC6265bis]에 의존합니다. 서버 측과 클라이언트 측 쿠키 기반 세션에는 모두 몇 가지 단점이 있습니다.

서버 측 세션은 세션 식별자만 노출하고 모든 데이터를 서버에 보관합니다. 이렇게 하면 모든 세션을 원하는 대로 폐기할 수 있는 기능과 함께 활성 세션을 높은 수준으로 제어할 수 있습니다. 이 접근 방식의 단점은 확장성에 미치는 영향이며, "고정 세션"이나 "세션 복제"와 같은 해결책이 필요합니다. 이러한 단점을 고려하면 BFF와 함께 서버 측 세션을 사용하는 것은 소규모 시나리오에서만 권고됩니다.

클라이언트 측 세션은 서명되고 선택적으로 암호화된 객체에 모든 데이터를 담아 브라우저로 보냅니다. 이 패턴을 사용하면 서버가 세션 데이터를 추적할 필요가 없지만 활성 세션에 대한 제어가 크게 제한되고 세션 폐기를 처리하기가 어려워집니다. 그러나 BFF 컨텍스트에서 클라이언트 측 세션을 사용하면 이러한 속성이 크게 달라집니다. 쿠키 기반 세션은 사용자의 토큰을 얻는 데만 사용되므로 모든 제어 및 폐기 속성은 액세스 토큰과 갱신 토큰의 사용에 따라 결정됩니다. 쿠키 기반 세션을 명시적으로 무효화하지 않고도 사용자의 액세스 토큰 및/또는 갱신 토큰을 폐기하는 것만으로 보호된 리소스에 대한 지속적인 액세스를 방지할 수 있습니다.

세션 쿠키를 보호하기 위한 모범 사례는 제 6.1.3.2절에서 설명합니다.

6.1.2.4. OAuth와 OpenID Connect 결합

이 애플리케이션 아키텍처에서 사용하는 OAuth 흐름은 인가 요청(C)에 필요한 OpenID Connect 범위를 포함하여 OpenID Connect와 결합할 수 있습니다([OpenID]의 제3.1.2.1절에 정의된 openid 범위 이상). 이 경우 BFF는 (F)에서 ID 토큰을 받습니다. BFF는 ID 토큰의 정보를 사용자 세션과 연결하고 (B) 또는 (I)에서 애플리케이션에 제공할 수 있습니다.

필요한 경우 BFF는 사용자 세션과 연결된 액세스 토큰을 사용하여 UserInfo 엔드포인트로 요청을 보낼 수 있습니다.

6.1.2.5. 실용적인 배포 전략

정적 JavaScript 코드를 제공하는 작업은 OAuth 토큰을 처리하고 요청을 전달하는 작업과 별개의 책임입니다. 그림 1에서는 BFF와 정적 웹 호스트를 두 개의 별도 주체로 표시합니다. 실제 배포에서는 이러한 구성 요소를 하나의 서비스(즉, 정적 JavaScript 코드를 제공하는 BFF), 두 개의 별도 서비스(즉, 콘텐츠 전송 네트워크(CDN)와 BFF) 또는 하나의 서비스 내 두 구성 요소 (즉, 클라우드 플랫폼의 정적 호스팅과 서버리스 함수)로 배포할 수 있습니다.

특정 시나리오를 지원하도록 이 아키텍처를 추가로 사용자 정의할 수 있다는 점에 유의하십시오. 예를 들어 내부 리소스 서버와 외부 리소스 서버에 모두 의존하는 애플리케이션은 내부 리소스 서버를 BFF와 함께 호스팅할 수 있습니다. 이 시나리오에서는 내부 리소스 서버에 대한 요청을 네트워크를 통해 전달할 필요 없이 BFF에서 직접 처리합니다. 사용자 세션이 내부적으로 액세스 토큰과 그 클레임으로 변환되므로 리소스 서버 관점의 인가는 변경되지 않습니다.

6.1.3. 보안 고려 사항

6.1.3.1. 인가 코드 권한 부여

BFF 사용의 주요 이점은 BFF가 기밀 클라이언트로 동작할 수 있다는 것입니다. 따라서 BFF는 인가 서버와 자격 증명을 설정하여 기밀 클라이언트로 반드시 동작해야 합니다. 또한 BFF는 액세스 토큰 요청을 시작하기 위해 제2.1.1절, [RFC9700]에서 설명한 OAuth 2.0 인가 코드 권한 부여를 반드시 사용해야 합니다.

6.1.3.3. 사이트 간 요청 위조 방지

브라우저 기반 애플리케이션과 BFF 간의 상호작용은 인증과 인가에 쿠키를 사용합니다. 다른 쿠키 기반 상호작용과 마찬가지로 BFF는 사이트 간 요청 위조(CSRF) 공격을 고려해야 합니다. CSRF 공격에 성공하면 공격자의 BFF 요청이 보호된 리소스로 나가는 호출을 트리거할 수 있습니다.

BFF는 적절한 CSRF 방어 수단을 반드시 구현해야 합니다. 정확한 메커니즘 또는 메커니즘의 조합은 아래에서 설명하는 것처럼 BFF가 배포되는 정확한 도메인에 따라 달라집니다.

6.1.3.3.2. 교차 오리진 리소스 공유

BFF는 CSRF 방어 메커니즘으로 CORS를 사용할 수 있습니다. CORS는 서버가 적절한 CORS 헤더를 설정하여 요청을 명시적으로 승인하지 않는 한 교차 오리진 요청을 제한하도록 브라우저에 구현된 보안 메커니즘입니다.

브라우저는 일반적으로 스크립트에서 시작된 교차 오리진 HTTP 요청을 제한합니다. 대상 서버가 요청을 승인하면 CORS가 이 제한을 해제할 수 있으며, 이는 초기 "프리플라이트" 요청을 통해 확인됩니다. 프리플라이트 응답에서 요청을 명시적으로 승인하지 않으면 브라우저는 전체 요청 전송을 거부합니다.

이러한 속성으로 인해 BFF는 CORS를 CSRF 방어 수단으로 사용할 수 있습니다. 공격자가 사용자의 브라우저에서 BFF로 교차 오리진 요청을 보내려고 하면 BFF가 프리플라이트 응답에서 요청을 승인하지 않으므로 브라우저가 실제 요청을 차단합니다. 공격자는 언제든 자신의 컴퓨터에서 요청을 보낼 수 있지만, 이 경우 요청에 사용자의 쿠키가 포함되지 않으므로 공격은 실패합니다.

CORS를 CSRF 방어 수단으로 사용할 때는 특정 요청이 프리플라이트 없이 가능하다는 점을 인식해야 합니다. "CORS 안전 목록 요청"이라고 하는 이러한 요청에서는 브라우저가 요청을 그대로 보내고 서버가 적절한 CORS 헤더를 전송하지 않았다면 응답에 대한 액세스를 차단합니다. 이 동작은 GET 요청이나 양식 기반 POST 요청처럼 JavaScript 이외의 수단을 통해 트리거될 수 있는 요청에 적용됩니다.

이 동작으로 인해 CORS를 방어 수단으로 활성화하더라도 리소스 서버의 특정 엔드포인트가 CSRF에 취약해질 수 있습니다. 예를 들어 리소스 서버가 본문 없는 POST 요청용 엔드포인트를 노출하는 API라면 프리플라이트 요청이 없으므로 CSRF 방어도 적용되지 않습니다.

이러한 CORS 정책 우회를 방지하기 위해 BFF는 브라우저 기반 애플리케이션이 사용자 정의 요청 헤더를 포함하도록 요구해야 합니다. 사용자 정의 요청 헤더가 있는 교차 오리진 요청은 항상 프리플라이트가 필요하므로 CORS가 효과적인 CSRF 방어 수단이 됩니다. 이 메커니즘을 사용할 때 BFF는 들어오는 모든 요청에 이 정적 헤더가 포함되어 있는지 반드시 확인해야 합니다. 이 헤더의 정확한 이름은 애플리케이션과 BFF가 결정할 수 있습니다. 구성 예로는 My-Static-Header: 1과 같은 정적 값을 가진 요청 헤더가 있습니다.

브라우저 기반 애플리케이션을 BFF와 동일한 오리진에 배포할 수도 있습니다. 이렇게 하면 프런트엔드와 BFF 사이의 적법한 상호작용에 프리플라이트가 필요하지 않으므로 추가적인 오버헤드가 없습니다.

6.1.3.3.3. 위조 방지/이중 제출 쿠키 사용

일부 기술 스택과 프레임워크에는 위조 방지 쿠키를 사용하는 CSRF 보호 기능이 내장되어 있습니다. 이 메커니즘은 쿠키에 저장된 세션별 비밀 값에 의존하며, 이 값은 쿠키와 연결된 도메인에서 실행되는 적법한 프런트엔드만 읽을 수 있습니다. 프런트엔드는 쿠키를 읽고 일반적으로 사용자 정의 요청 헤더를 추가하여 그 값을 요청에 삽입해야 합니다. 백엔드는 적법한 요청을 식별하기 위해 쿠키의 값과 프런트엔드에서 제공한 값을 비교하여 검증합니다. 상태를 변경하는 모든 요청에 올바르게 구현하면 이 메커니즘은 CSRF를 효과적으로 완화합니다.

이 메커니즘이 반드시 CORS 방식보다 권고되는 것은 아닙니다. 그러나 프레임워크에서 이 메커니즘을 기본으로 지원한다면 적은 노력으로 CSRF를 방지할 수 있는 대안이 될 수 있습니다.

6.1.3.4. BFF 아키텍처의 개인정보 보호 고려 사항

BFF 패턴을 사용할 때 브라우저 기반 애플리케이션은 백엔드 BFF 구성 요소를 통해 리소스 서버로 모든 요청을 전달해야 합니다. 따라서 BFF 구성 요소는 애플리케이션과 리소스 서버 사이의 모든 요청과 응답을 관찰할 수 있으며, 이는 개인정보 보호에 상당한 영향을 미칠 수 있습니다.

브라우저 기반 애플리케이션과 BFF를 동일한 당사자가 구축하고 배포한다면 개인정보 보호에 미치는 영향은 미미할 가능성이 높습니다. 그러나 서드 파티가 제공하거나 호스팅하는 BFF 구성 요소를 사용하여 이 패턴을 구현할 때는 개인정보 보호에 미치는 영향을 고려해야 합니다.

6.1.3.5. 운영 고려 사항

BFF가 프런트엔드를 대신하여 모든 요청을 리소스 서버로 전달하므로, 리소스 서버가 이 구성 요소를 인식하고 속도 제한 및 기타 악용 방지 조치에 적합한 정책을 사용하도록 주의해야 합니다. 예를 들어 BFF가 단일 인스턴스 서비스로 배포되고 리소스 서버가 IP 주소를 기준으로 요청 속도를 제한한다면, 많은 사용자의 브라우저가 BFF의 단일 IP 주소에서 오는 것으로 보이기 때문에 서버가 요청을 차단하기 시작할 수 있습니다.

6.1.3.6. 프록시 제한

BFF는 프런트엔드의 요청을 받아 리소스 서버로 전달하는 프록시 서비스로 동작합니다. 들어오는 요청에는 쿠키가 포함되며, BFF는 나가는 요청에서 이를 액세스 토큰으로 변환합니다. (이 때문에 HTTP 프록시보다는 애플리케이션 계층 역방향 프록시에 더 가깝다는 점에 유의하십시오.) CSRF 공격 외에도 공격자는 BFF가 의도하지 않은 호스트로 요청을 전달하도록 조작하려 할 수 있습니다. 공격자가 이를 성공적으로 악용하면 BFF를 임의의 서버로 리디렉션하여 사용자의 액세스 토큰이 노출될 수 있습니다.

이러한 위험을 완화하기 위해 BFF는 요청을 전달하기 전에 대상 호스트를 검증하여 엄격한 나가는 요청 제어를 반드시 적용해야 합니다. 이를 위해서는 승인된 리소스 서버의 명시적인 허용 목록을 유지하여 요청이 사전에 정의된 백엔드로만 프록시되도록 보장해야 합니다(예: /bff/orders/create는 https://order-api.example.com/create에만 매핑됨). 경로에 따른 동적 라우팅 (예: /bff/orders/{id})이 필요하다면 BFF는 인가된 대상에만 액세스할 수 있도록 엄격한 검증을 반드시 적용해야 합니다. 또한 엔드포인트별로 허용되는 HTTP 메서드를 제한하면 공격 벡터를 더욱 줄일 수 있습니다.

동적으로 구성할 수 있는 프록시를 구현할 때 BFF는 명시적으로 허용된 호스트와 경로에 대한 요청만 허용하도록 반드시 보장해야 합니다. 이러한 제한을 적용하지 않으면 무단 액세스와 액세스 토큰 유출이 발생할 수 있습니다.

6.1.3.7. 고급 보안

BFF 패턴에서는 모든 OAuth 관련 작업이 기밀 클라이언트로 동작하는 서버 측 구성 요소인 BFF로 이동합니다. 서버 측 애플리케이션은 브라우저 기반 애플리케이션보다 더 통제된 환경에서 실행되므로 고급 OAuth 보안 사례를 도입하기가 더 쉬워집니다. 예로는 키 기반 클라이언트 인증과 송신자 제한 토큰이 있습니다.

6.1.4. 위협 분석

이 절에서는 제5절의 공격 시나리오와 결과를 다시 살펴보고 잠재적인 추가 방어 수단을 설명합니다.

6.1.4.1. 공격 시나리오 및 결과

공격자가 애플리케이션의 실행 컨텍스트에서 악성 코드 (예: JavaScript 또는 WASM)를 실행할 수 있다면 다음 공격 시나리오가 관련됩니다.

  • 사용자 브라우저를 통한 요청 프록시 처리(제 5.1.4절)

이 공격 시나리오는 다음 결과를 초래한다는 점에 유의하십시오.

클라이언트 하이재킹은 브라우저 기반 애플리케이션의 특성에 내재된 공격 시나리오라는 점에 유의하십시오. 따라서 처음부터 악성 코드의 실행을 차단하는 것 외에는 이러한 공격을 방지할 수 있는 방법이 없습니다. 이를 달성하는 데 도움이 되는 기법에는 안전한 코딩 지침 준수, 코드 분석 및 콘텐츠 보안 정책 [W3C.CSP3]과 같은 심층 방어 메커니즘 배포가 있습니다.

이 아키텍처에서 BFF는 다양한 보안 관련 책임과 프록시 기반 동작을 처리하는 핵심 구성 요소입니다. 프록시 기반 애플리케이션의 안전한 구현을 논의하는 것은 이 문서의 범위를 벗어나지만, BFF의 보안 취약점이 애플리케이션에 상당한 영향을 미칠 수 있다는 점에 유의해야 합니다.

마지막으로 BFF는 브라우저 기반 애플리케이션과 리소스 서버 사이의 모든 트래픽을 관찰하기에 적합한 고유한 위치에 있습니다. 높은 보안 수준이 필요한 애플리케이션에서 이상 징후 탐지나 속도 제한을 구현하려 한다면 BFF가 이를 수행하기에 이상적인 위치입니다. 이러한 제한은 클라이언트 하이재킹의 결과를 더욱 완화하는 데 도움이 될 수 있습니다.

6.1.4.2. 완화된 공격 시나리오

아래에 나열된 다른 공격 시나리오는 BFF 애플리케이션 아키텍처를 통해 효과적으로 완화됩니다.

BFF는 브라우저 기반 애플리케이션에 어떠한 토큰도 노출하지 않아 처음 두 공격 시나리오에 대응합니다. 공격자가 애플리케이션을 완전히 제어하더라도 탈취할 토큰이 전혀 없습니다.

공격자가 자동 흐름을 실행하여 새로운 액세스 토큰(및 선택적으로 갱신 토큰)을 얻는 세 번째 시나리오는 BFF를 기밀 클라이언트로 만들어 완화합니다. 공격자가 인가 코드를 얻더라도 클라이언트 자격 증명이 없으므로 이 코드를 교환할 수 없습니다. 또한 PKCE를 사용하면 인가 코드에 대한 다른 공격을 방지할 수 있습니다.

갱신 토큰과 액세스 토큰은 BFF에서 관리하며 브라우저에 노출되지 않으므로 잠재적인 공격이 초래하는 다음 두 결과는 관련이 없습니다.

6.1.4.3. 요약

BFF 아키텍처는 브라우저 전용 애플리케이션보다 훨씬 복잡합니다. 서버 측 BFF 구성 요소를 배포하고 운영해야 합니다. 또한 이 패턴에서는 애플리케이션과 리소스 서버 사이의 모든 상호작용을 BFF가 프록시해야 합니다. 배포 패턴에 따라 이러한 프록시 동작이 서버 측 구성 요소에 상당한 부담을 줄 수 있습니다. BFF가 리소스 서버로 동작하는 경우의 추가 참고 사항은 제 6.2.2.6절을 참조하십시오.

그러나 BFF 아키텍처 패턴의 특성으로 인해 강력한 보안을 보장합니다. 또한 BFF를 사용하면 OAuth 사용으로 인해 애플리케이션의 공격 표면이 증가하지 않습니다. 실행 가능한 유일한 공격 패턴은 사용자의 브라우저에서 클라이언트 애플리케이션을 하이재킹하는 것으로, 이는 웹 애플리케이션에 내재된 문제입니다.

이 아키텍처는 비즈니스 애플리케이션, 민감한 애플리케이션 및 개인정보를 처리하는 애플리케이션에 적극 권고됩니다.

6.2. 토큰 중개 백엔드

이 절에서는 기밀 클라이언트(제2.1절, [RFC6749]에 정의됨)로서 토큰을 얻는 OAuth 관련 작업을 처리하기 위해 백엔드 구성 요소에 의존하는 브라우저 기반 애플리케이션의 아키텍처를 설명합니다. 이후 백엔드 구성 요소는 애플리케이션이 리소스 서버와 직접 상호작용할 수 있도록 액세스 토큰을 제공합니다.

토큰 중개 백엔드 패턴은 애플리케이션과 리소스 서버 사이의 모든 요청과 응답을 프록시할 필요가 없으므로 BFF 패턴(제6.1절 참조)보다 가볍습니다. 보안 관점에서 토큰 중개 백엔드는 BFF보다 안전하지 않지만, 브라우저에서 직접 실행되는 OAuth 클라이언트 애플리케이션보다 여전히 상당한 이점을 제공합니다.

공격자가 애플리케이션 내부에서 악성 코드를 실행할 수 있더라도 이 애플리케이션 아키텍처는 공격자가 갱신 토큰을 악용하거나("단일 실행 토큰 탈취"(제5.1.1절) 및 "지속적인 토큰 탈취"(제5.1.2절) 참조) 새로운 토큰 집합을 얻는 것을 방지할 수 있습니다("새로운 토큰의 획득 및 추출"(제5.1.3절) 참조). 그러나 액세스 토큰이 애플리케이션에 직접 노출되므로 공격자는 클라이언트 측 저장소에서 토큰을 탈취하거나("단일 실행 토큰 탈취"(제5.1.1절) 및 "지속적인 토큰 탈취"(제5.1.2절) 참조) 토큰 중개 백엔드에서 새 토큰을 요청할 수 있습니다("사용자 브라우저를 통한 요청 프록시 처리" (제5.1.4절) 참조). HttpOnly 쿠키를 사용하면 공격자가 세션 상태에 직접 액세스하지 못하므로 액세스 토큰 탈취가 세션 하이재킹으로 확대되는 것을 방지할 수 있습니다.

6.2.1. 애플리케이션 아키텍처

인가 토큰 리소스 엔드포인트 엔드포인트 서버 (F) 토큰 중개 백엔드 (J) (D) (B,I) (C) (E) (G) (A,H) 정적 웹 호스트 브라우저
그림 2: OAuth 2.0 토큰 중개 백엔드 패턴

이 아키텍처에서는 브라우저 기반 코드(예: JavaScript 또는 WASM)가 먼저 정적 웹 호스트에서 브라우저로 로드되고(A), 이후 애플리케이션이 브라우저에서 실행됩니다. 애플리케이션은 활성 세션이 있는지 토큰 중개 백엔드에 확인합니다(B). 활성 세션이 발견되면 애플리케이션은 해당 액세스 토큰을 받고 인증된 상태를 재개한 다음 (J)로 건너뜁니다.

활성 세션을 찾지 못하면 애플리케이션은 PKCE 확장을 사용하는 인가 코드 흐름을 시작하기 위해 토큰 중개 백엔드로 이동합니다(C). 이에 관한 내용은 제6.2.3.1절에서 설명하며, 토큰 중개 백엔드는 브라우저를 인가 엔드포인트로 리디렉션하여 응답합니다(D). 사용자가 다시 리디렉션되면 브라우저는 인가 코드를 토큰 중개 백엔드로 전달하고(E), 토큰 중개 백엔드는 클라이언트 자격 증명과 PKCE 코드 검증기를 사용하여 토큰 엔드포인트에서 인가 코드를 토큰으로 교환할 수 있습니다(F).

토큰 중개 백엔드는 얻은 토큰을 사용자 세션과 연결하고(제 6.2.2.4절 참조), 이 세션을 추적하기 위해 응답에 쿠키를 설정합니다(G). 브라우저에 대한 이 응답은 애플리케이션의 재로드도 트리거합니다(H). 애플리케이션이 재로드되면 기존 세션이 있는지 토큰 중개 백엔드에 확인하여(I) 애플리케이션이 인증된 상태를 재개하고 토큰 중개 백엔드에서 액세스 토큰을 얻을 수 있도록 합니다.

브라우저의 애플리케이션은 (I)에서 얻은 액세스 토큰을 사용하여 리소스 서버로 직접 요청을 보낼 수 있습니다(J).

6.2.2. 구현 세부 사항

6.2.2.1. 세션 및 OAuth 엔드포인트

토큰 중개 백엔드 엔드포인트의 구현 대부분은 BFF에 관해 설명한 구현과 유사합니다.

  • "check session" 엔드포인트(그림 2의 (B)와 (I))는 브라우저 기반 애플리케이션이 호출하는 API 엔드포인트입니다. 사용 가능한 경우 요청에는 세션 정보가 포함되므로 백엔드가 활성 세션을 확인할 수 있습니다. 응답은 브라우저 기반 애플리케이션에 세션이 활성 상태인지 알려야 합니다. 활성 세션이 발견되면 백엔드는 응답에 액세스 토큰을 포함합니다. 또한 백엔드는 인증된 사용자의 신원 정보와 같은 다른 정보도 포함할 수 있습니다.

  • 인가 코드 흐름을 시작하는 엔드포인트 (C)는 BFF 아키텍처에서 설명한 엔드포인트와 동일합니다. 자세한 내용은 제6.1.2.1절을 참조하십시오.

  • 인가 코드를 받는 엔드포인트 (E)는 BFF 아키텍처에서 설명한 엔드포인트와 동일합니다. 자세한 내용은 제6.1.2.1절을 참조하십시오.

  • 로그아웃을 지원하는 엔드포인트는 BFF 아키텍처에서 설명한 엔드포인트와 동일합니다. 자세한 내용은 제6.1.2.1절을 참조하십시오.

6.2.2.2. 갱신 토큰

제4.14절, [RFC9700]에서 설명한 대로 갱신 토큰을 사용할 때 토큰 중개 백엔드는 (F)에서 갱신 토큰을 얻고 이를 사용자 세션과 연결합니다.

리소스 서버가 액세스 토큰을 거부하면 애플리케이션은 토큰 중개 백엔드에 접속하여 새 액세스 토큰을 요청할 수 있습니다. 토큰 중개 백엔드는 이 요청과 연결된 쿠키를 사용하여 사용자의 갱신 토큰을 조회하고 갱신 토큰을 사용해 토큰 요청을 보냅니다. 이러한 단계는 그림 2에 표시되지 않습니다. 이 갱신 토큰 요청은 기밀 클라이언트인 백엔드에서 전송하므로 클라이언트 인증이 필요하다는 점에 유의하십시오.

갱신 토큰이 만료되면 완전히 새로운 인가 코드 권한 부여를 시작하지 않고서는 유효한 액세스 토큰을 얻을 방법이 없습니다. 따라서 해당 정보를 미리 알고 있다면 쿠키 기반 세션의 수명을 갱신 토큰의 최대 수명과 동일하게 구성하는 것이 합리적입니다. 또한 토큰 중개 백엔드가 활성 세션의 갱신 토큰이 더 이상 유효하지 않다는 사실을 알게 되면 세션을 무효화하는 것이 합리적입니다.

6.2.2.3. 액세스 토큰 범위

액세스하는 리소스 서버와 인가 서버의 범위 구성에 따라 애플리케이션은 서로 다른 범위 구성을 가진 액세스 토큰을 요청할 수 있습니다. 이 동작을 통해 애플리케이션은 최소한의 범위가 지정된 액세스 토큰을 사용하는 모범 사례를 따를 수 있습니다.

애플리케이션은 활성 세션을 확인할 때((A) 및 (I)) 원하는 범위를 토큰 중개 백엔드에 알릴 수 있습니다. 이전에 얻은 액세스 토큰이 원하는 범위 기준을 충족하는지는 토큰 중개 백엔드가 결정합니다.

토큰 중개 백엔드의 이러한 액세스 토큰 캐싱 메커니즘을 무분별하게 적용하면 범위 상승 위험이 발생할 수 있다는 점에 유의해야 합니다. 캐시된 액세스 토큰의 범위가 프런트엔드에서 요청한 범위의 상위 집합이라면 토큰 중개 백엔드는 이를 프런트엔드로 반환해서는 안 됩니다. 대신 갱신 토큰을 사용하여 인가 서버로부터 더 작은 범위 집합을 가진 액세스 토큰을 요청해야 합니다. 이러한 액세스 토큰 범위 축소 메커니즘의 지원 여부는 인가 서버가 결정한다는 점에 유의하십시오.

토큰 중개 백엔드는 특정 리소스 서버용 액세스 토큰을 얻기 위해 [RFC8707]에 설명된 확장에 의존할 때 범위 축소 메커니즘과 유사한 메커니즘을 사용할 수 있습니다.

BFF와 마찬가지로 토큰 중개 백엔드는 사용자 세션을 추적하기 위해 브라우저 쿠키에 의존합니다. 쿠키 기반 세션의 구현 지침과 보안 고려 사항은 제6.1.2.3절에서 설명한 BFF의 경우와 동일합니다.

6.2.2.5. OAuth와 OpenID Connect 결합

BFF와 마찬가지로 토큰 중개 백엔드는 OAuth와 OpenID Connect를 단일 흐름으로 결합할 수 있습니다. 자세한 내용은 제6.1.2.4절을 참조하십시오.

6.2.2.6. 실용적인 배포 시나리오

정적 JavaScript 또는 WASM 코드를 호스팅하는 것은 인가 서버와의 상호작용을 처리하는 것과 별개의 책임입니다. 그림 2에서는 토큰 중개 백엔드와 정적 웹 호스트를 별도의 두 주체로 표시합니다. 실제 배포 시나리오에서는 이러한 구성 요소를 하나의 서비스(즉, 정적 코드를 제공하는 토큰 중개 백엔드), 두 개의 별도 서비스(즉, CDN과 토큰 중개 백엔드) 또는 하나의 서비스 내 두 구성 요소(즉, 클라우드 플랫폼의 정적 호스팅과 서버리스 함수)로 배포할 수 있습니다. 이러한 배포상의 차이는 이 패턴에서 설명한 관계에 영향을 주지 않지만, 교차 오리진 통신을 활성화하기 위한 올바른 CORS 구성의 필요성과 같은 다른 실무적인 사항에는 영향을 줄 수 있습니다.

6.2.3. 보안 고려 사항

6.2.3.1. 인가 코드 권한 부여

토큰 중개 백엔드 사용의 주요 이점은 백엔드가 기밀 클라이언트로 동작할 수 있다는 것입니다. 따라서 토큰 중개 백엔드는 기밀 클라이언트로 반드시 동작해야 합니다. 또한 토큰 중개 백엔드는 액세스 토큰 요청을 시작하기 위해 제2.1.1절, [RFC9700]에 설명된 OAuth 2.0 인가 코드 권한 부여를 반드시 사용해야 합니다.

6.2.3.3. 사이트 간 요청 위조 방지

브라우저 기반 애플리케이션과 토큰 중개 백엔드 간의 상호작용은 인증과 인가에 쿠키를 사용합니다. BFF와 마찬가지로 토큰 중개 백엔드는 CSRF 공격을 고려해야 합니다.

제6.1.3.3절에서는 다양한 CSRF 공격 완화 전략의 세부적인 차이를 설명합니다. 특히 토큰 중개 백엔드에서 이러한 CSRF 방어 수단은 애플리케이션이 액세스 토큰을 얻을 수 있는 엔드포인트에만 적용됩니다.

6.2.3.4. 고급 OAuth 보안

토큰 중개 백엔드는 서버 측 구성 요소로 실행되는 기밀 클라이언트입니다. 토큰 중개 백엔드는 키 기반 클라이언트 인증과 같은 기밀 클라이언트용 보안 모범 사례를 도입할 수 있습니다.

6.2.4. 위협 분석

이 절에서는 제5절의 공격 시나리오와 결과를 다시 살펴보고 잠재적인 추가 방어 수단을 설명합니다.

6.2.4.1. 공격 시나리오 및 결과

공격자가 애플리케이션의 실행 컨텍스트에서 악성 코드를 실행할 수 있다면 다음 공격 시나리오가 관련됩니다.

  • 액세스 토큰에 대한 단일 실행 토큰 탈취(제 5.1.1절)

  • 액세스 토큰에 대한 지속적인 토큰 탈취(제 5.1.2절)

  • 사용자 브라우저를 통한 요청 프록시 처리(제 5.1.4절)

이러한 공격 시나리오는 다음 결과를 초래한다는 점에 유의하십시오.

액세스 토큰을 브라우저 기반 애플리케이션에 노출하는 것은 토큰 중개 백엔드 아키텍처 패턴의 핵심 개념입니다. 따라서 액세스 토큰은 악성 브라우저 기반 코드에 의한 토큰 탈취에 취약해집니다.

6.2.4.2. 완화된 공격 시나리오

아래에 나열된 다른 공격 시나리오는 토큰 중개 백엔드를 통해 효과적으로 완화됩니다.

토큰 중개 백엔드는 브라우저 기반 애플리케이션에 갱신 토큰을 노출하지 않아 처음 두 공격 시나리오에 대응합니다. 공격자가 애플리케이션을 완전히 제어하더라도 탈취할 갱신 토큰이 전혀 없습니다.

공격자가 자동 흐름을 실행하여 새로운 액세스 토큰(및 선택적으로 갱신 토큰)을 얻는 세 번째 시나리오는 토큰 중개 백엔드를 기밀 클라이언트로 만들어 완화합니다. 공격자가 인가 코드를 얻더라도 클라이언트 자격 증명이 없으므로 이 코드를 교환할 수 없습니다. 또한 PKCE를 사용하면 인가 코드에 대한 다른 공격을 방지할 수 있습니다.

토큰 중개 백엔드의 특성으로 인해 잠재적인 공격이 초래하는 다음 결과는 관련이 없습니다.

6.2.4.3. 추가 방어 수단

이 아키텍처는 본질적으로 액세스 토큰을 노출하지만 애플리케이션의 보안 태세를 강화하는 데 도움이 되는 몇 가지 추가 방어 수단이 있습니다.

6.2.4.3.1. 안전한 토큰 저장

토큰 중개 백엔드 패턴의 특성상 브라우저에 토큰을 영구적으로 저장할 필요가 없습니다. 필요한 경우 애플리케이션은 언제든 쿠키 기반 세션을 사용하여 토큰 중개 백엔드에서 액세스 토큰을 얻을 수 있습니다. 제8절에서는 브라우저의 다양한 저장 메커니즘이 지닌 보안 속성을 자세히 설명합니다.

액세스 토큰을 악성 브라우저 기반 코드가 접근할 수 없는 곳에 저장하더라도 악성 코드는 적법한 애플리케이션을 모방하고 토큰 중개 백엔드로 요청을 보내 최신 액세스 토큰을 얻을 수 있다는 점에 유의하십시오.

6.2.4.3.2. 송신자 제한 토큰 사용

이 아키텍처에서 송신자 제한 액세스 토큰을 사용하는 것은 간단하지 않습니다. 토큰 중개 백엔드는 인가 코드나 갱신 토큰을 액세스 토큰으로 교환하지만, 액세스 토큰은 애플리케이션에서 사용합니다. DPoP [RFC9449]와 같은 메커니즘을 사용하려면 책임을 두 당사자에게 나누어야 하지만, 이는 명세에서 정의한 시나리오가 아닙니다. 이러한 시나리오에서 DPoP를 사용하는 것은 이 문서의 범위를 벗어납니다.

6.2.4.4. 요약

토큰 중개 백엔드의 아키텍처는 브라우저 전용 애플리케이션보다 복잡하지만 프록시 BFF를 실행하는 것보다는 덜 복잡합니다. 마찬가지로 토큰 중개 백엔드가 제공하는 보안 속성도 BFF를 사용하는 경우와 브라우저 전용 애플리케이션을 실행하는 경우 사이에 있습니다.

토큰 중개 백엔드는 공격자에게 사용자를 대신하는 장기적인 액세스를 부여하는 일반적인 시나리오에 대응합니다. 그러나 액세스 토큰 탈취의 결과로 공격자는 여전히 리소스 서버에 직접 액세스할 수 있습니다.

토큰 중개 백엔드 아키텍처를 고려할 때는 제6.1절에서 설명한 완전한 BFF를 도입하는 것이 실행 가능한 대안인지 평가할 것을 적극 권고합니다. 사용 사례나 시스템 요구 사항으로 인해 프록시 BFF를 사용할 수 없는 경우에만 완전한 BFF 대신 토큰 중개 백엔드를 고려해야 합니다.

6.3. 브라우저 기반 OAuth 2.0 클라이언트

이 절에서는 OAuth 클라이언트로 동작하면서 브라우저에서 모든 OAuth 관련 작업을 처리하는 브라우저 기반 애플리케이션의 아키텍처를 설명합니다. 따라서 브라우저 기반 애플리케이션은 백엔드 구성 요소의 개입 없이 인가 서버로부터 토큰을 얻습니다.

공격자가 브라우저에서 악성 코드를 실행할 수 있다면 이 애플리케이션 아키텍처는 앞서 설명한 모든 공격 시나리오(제5.1절)에 취약합니다. 본질적으로 공격자는 인가 서버로부터 액세스 토큰과 갱신 토큰을 얻을 수 있으며, 사용자를 대신하여 보호된 리소스에 장기간 액세스할 가능성이 있습니다.

6.3.1. 애플리케이션 아키텍처

인가 리소스 서버 서버 (B) (C) (D) (E) (A) 정적 웹 호스트 브라우저
그림 3: 브라우저 기반 OAuth 2.0 클라이언트 패턴

이 아키텍처에서는 코드가 먼저 정적 웹 호스트에서 브라우저로 로드되고(A), 이후 애플리케이션이 브라우저에서 실행됩니다. 이 시나리오에서 브라우저 기반 애플리케이션은 인가 서버에 인증할 클라이언트 자격 증명을 보유하지 않은 공개 클라이언트로 간주됩니다.

애플리케이션은 PKCE 확장을 사용하는 인가 코드 흐름을 시작하여 인가 코드를 얻습니다(B). 이에 관한 내용은 제6.3.2.1절에서 설명합니다. 애플리케이션은 브라우저 API (예: [Fetch])를 사용하여 토큰 엔드포인트로 POST 요청을 보내고(C) 인가 코드를 토큰으로 교환합니다.

이후 애플리케이션은 제8절에 설명된 대로 적절한 브라우저 API를 사용하여 액세스 토큰과 선택적인 갱신 토큰을 최대한 안전하게 저장할 책임이 있습니다.

브라우저의 애플리케이션이 리소스 서버로 요청을 보내려 할 때는 리소스 서버와 직접 상호작용할 수 있습니다. 애플리케이션은 요청에 액세스 토큰을 포함하고(D) 리소스 서버의 응답을 받습니다(E).

6.3.2. 구현 세부 사항

공개 클라이언트(제 2.1절, [RFC6749])이며 제 4.1절, [RFC6749]에 설명된 인가 코드 권한 부여 유형을 사용하는 브라우저 기반 애플리케이션은 이 절에 설명된 추가 요구 사항도 반드시 따라야 합니다.

6.3.2.1. 인가 코드 권한 부여

공개 클라이언트인 브라우저 기반 애플리케이션은 액세스 토큰을 얻을 때 PKCE [RFC7636] 확장을 반드시 구현해야 하며, 인가 서버는 이러한 클라이언트에 PKCE를 반드시 지원하고 적용해야 합니다.

PKCE 확장은 인가 서버가 인가 코드를 교환하는 클라이언트 인스턴스와 흐름을 시작한 인스턴스가 동일한지 검증할 수 있도록 하여 인가 코드가 가로채지고 악성 클라이언트에 의해 액세스 토큰으로 교환되는 공격을 방지합니다.

6.3.2.2. 사이트 간 요청 위조 방지

브라우저 기반 애플리케이션은 리디렉션 URI에 대한 CSRF 공격을 반드시 방지해야 합니다. 다음 방법 중 하나로 이를 수행할 수 있습니다.

  • 이 클라이언트에 PKCE를 요구하도록 인가 서버를 구성합니다.

  • CSRF 토큰을 전달하도록 OAuth state 매개변수에 고유한 값을 사용하고 검증합니다.

  • 애플리케이션이 OpenID Connect도 사용한다면 [OpenID]에 설명된 OpenID Connect nonce 매개변수를 사용하고 검증합니다.

인가 코드 흐름에 적절한 CSRF 방어 수단을 선택하는 방법에 관한 자세한 내용은 제2.1절, [RFC9700]을 참조하십시오.

6.3.2.3. 갱신 토큰

애플리케이션이 DPoP [RFC9449]를 명시적으로 사용하지 않는 한, 브라우저 기반 클라이언트의 갱신 토큰은 일반적으로 베어러 토큰입니다. 따라서 유출된 갱신 토큰의 위험은 유출된 액세스 토큰보다 큽니다. 공격자가 탈취한 갱신 토큰을 계속 사용하여 인가 서버에서 탐지되지 않은 채 새로운 액세스 토큰을 얻을 가능성이 있기 때문입니다.

인가 서버는 브라우저 기반 애플리케이션에 갱신 토큰을 발급할지 선택할 수 있습니다. 그러나 서드 파티 쿠키 차단 메커니즘의 영향을 고려하면 갱신 토큰 사용의 이점이 훨씬 커졌습니다. [RFC9700]에서는 [RFC6749]의 권고 사항에 더하여 갱신 토큰에 관한 몇 가지 추가 요구 사항을 설명합니다. 이 BCP를 준수하는 애플리케이션과 인가 서버는 브라우저 기반 애플리케이션에 갱신 토큰을 발급하는 경우 [RFC9700]의 갱신 토큰 관련 권고 사항도 반드시 따라야 합니다.

특히 인가 서버는 다음 요구 사항을 따라야 합니다.

  • 갱신 토큰을 사용할 때마다 반드시 순환시키거나 제4.14.2절, [RFC9700]에 설명된 송신자 제한 갱신 토큰을 반드시 사용해야 합니다.

  • 갱신 토큰에 최대 수명을 설정하거나 일정 기간 갱신 토큰이 사용되지 않으면 만료되도록 반드시 해야 합니다.

  • 갱신 토큰에 미리 설정된 만료 시간이 있다면 순환된 갱신 토큰을 발급할 때 새 갱신 토큰의 수명을 최초 갱신 토큰의 수명 이후로 연장해서는 안 됩니다.

전체 갱신 토큰 수명을 최초 갱신 토큰의 수명으로 제한하면 탈취된 갱신 토큰이 무기한 사용되지 않도록 보장할 수 있습니다.

예를 들면 다음과 같습니다.

  • 사용자가 애플리케이션을 인가하여 10분 동안 유효한 액세스 토큰과 8 시간 동안 유효한 갱신 토큰이 발급됩니다.

  • 10분 후 최초 액세스 토큰이 만료되므로 애플리케이션이 갱신 토큰을 사용하여 새 액세스 토큰을 얻습니다.

  • 인가 서버는 10분 동안 유효한 새 액세스 토큰과 7시간 50분 동안 유효한 새 갱신 토큰을 반환합니다.

  • 최초 인가 시점으로부터 8시간이 지날 때까지 이 과정이 계속됩니다.

  • 이 시점에 애플리케이션이 8시간 후 갱신 토큰을 사용하려 하면 요청이 실패하며, 애플리케이션은 사용자의 인증 또는 이전에 설정된 세션에 의존하는 인가 코드 흐름을 다시 시작해야 합니다.

인가 서버는 갱신 토큰의 수명을 인가 서버에 설정된 사용자의 인증 세션과 연결해야 합니다. 이렇게 하면 사용자가 로그아웃할 때 브라우저 기반 애플리케이션에 이전에 발급된 갱신 토큰이 무효화되어 단일 로그아웃 시나리오와 같은 동작을 합니다. 인가 서버는 브라우저 기반 애플리케이션과 다른 공개 클라이언트에 서로 다른 갱신 토큰 발급, 수명 및 만료 정책을 설정할 수 있습니다.

6.3.3. 보안 고려 사항

6.3.3.1. 클라이언트 인증

브라우저 기반 애플리케이션의 소스 코드는 최종 사용자의 브라우저로 전달되므로 프로비저닝된 비밀 값을 포함하기에 적합하지 않습니다. 따라서 브라우저 기반 애플리케이션은 일반적으로 제2.1절, [RFC6749]에 정의된 공개 클라이언트로 배포됩니다.

여러 사용자에게 배포되는 앱의 일부로 정적으로 포함된 비밀 값은 기밀 비밀 값으로 취급해서는 안 됩니다. 한 사용자가 자신의 사본을 검사하여 공유 비밀 값을 알아낼 수 있기 때문입니다. 이러한 이유와 제5.3.1절, [RFC6819]에 명시된 이유로 인가 서버는 공유 비밀 값을 사용하는 브라우저 기반 애플리케이션의 클라이언트 인증을 요구해서는 안 됩니다. 이는 client_id 매개변수에서 이미 제공하는 클라이언트 식별 이외에는 아무런 가치가 없기 때문입니다.

SPA 클라이언트에 정적으로 포함된 공유 비밀 값을 여전히 요구하는 인가 서버는 해당 클라이언트를 공개 클라이언트로 반드시 취급해야 하며 그 비밀 값을 클라이언트 신원의 증거로 받아들여서는 안 됩니다. 추가 조치가 없다면 이러한 클라이언트는 클라이언트 사칭의 대상이 됩니다 (제 6.3.3.2절 참조).

6.3.3.2. 클라이언트 사칭

제10.2절, [RFC6749]에 명시된 대로 인가 서버는 클라이언트 애플리케이션의 신원을 보장할 수 있는 경우를 제외하고 사용자의 동의나 상호작용 없이 인가 요청을 자동으로 처리해서는 안 됩니다.

인가 서버가 리디렉션 URI를 와일드카드 도메인, 와일드카드 경로 또는 와일드카드 쿼리 문자열 구성 요소의 사용을 방지하는 고정된 절대 HTTPS URI 집합으로 제한한다면, 등록된 절대 HTTPS URI의 이러한 정확한 일치를 인가 서버에서 client_id에 대한 이전 요청이 이미 승인된 경우 인가 요청을 자동으로 처리할지 결정하기 위한 클라이언트 신원의 증거로 받아들일 수 있습니다.

6.3.3.2.1. 인가 코드 리디렉션

클라이언트는 인가 서버에 하나 이상의 리디렉션 URI를 반드시 등록해야 하며, 인가 요청에서는 정확하게 등록된 리디렉션 URI만 사용해야 합니다.

인가 서버는 제4.1.1절, [RFC9700]에 설명된 대로 등록된 리디렉션 URI의 정확한 일치를 반드시 요구해야 합니다. 이는 인가 코드를 대상으로 하는 공격을 방지하는 데 도움이 됩니다.

6.3.3.3. 브라우저 내 통신 흐름의 보안

브라우저 기반 애플리케이션에서는 기본 창을 리디렉션하는 대신 팝업이나 iframe 같은 보조 창에서 OAuth 흐름을 실행하는 것이 일반적입니다. 이러한 흐름에서 브라우저 기반 앱은 페이지 새로고침을 방지하거나 프레임 기반 흐름을 자동으로 실행하는 등의 목적으로 기본 창을 계속 제어합니다.

브라우저 기반 앱과 인가 서버가 서로 다른 프레임에서 호출된다면 최상위 리디렉션 대신 postMessage API(즉, [WebMessaging])와 같은 브라우저 내 통신 기법을 사용해야 합니다. 메시지의 기밀성과 진위성을 보장하려면 postMessage의 발신자 오리진과 수신자 오리진을 모두 postMessage API에서 기본적으로 제공하는 메커니즘을 사용하여 반드시 검증해야 합니다 ([WebMessaging]의 제9.3.2절).

제4.17절, [RFC9700]에서는 브라우저 내 통신 흐름의 보안과 이러한 공격을 방어하기 위해 브라우저 기반 애플리케이션과 인가 서버에서 반드시 적용해야 하는 대응책을 자세히 설명합니다.

6.3.3.4. 교차 오리진 요청

이 시나리오에서 애플리케이션은 브라우저 API를 사용하여 인가 서버와 리소스 서버로 요청을 보냅니다. OAuth 2.0의 특성상 이러한 요청은 일반적으로 교차 오리진 요청이므로 브라우저에서 적용하는 교차 오리진 통신 제한의 대상이 됩니다. 인가 서버와 리소스 서버는 애플리케이션이 필요한 교차 오리진 요청을 보낼 수 있도록 필요한 CORS 헤더([Fetch]에 정의됨)를 반드시 전송해야 합니다. 브라우저 기반 OAuth 클라이언트가 인가 서버 또는 리소스 서버와 동일한 오리진에서 실행되는 예외적인 시나리오에서는 필요한 상호작용을 활성화하기 위한 CORS 정책이 필요하지 않다는 점에 유의하십시오.

인가 서버의 CORS 구성은 브라우저 기반 애플리케이션이 인가 코드를 토큰으로 교환하는 토큰 엔드포인트와 관련이 있습니다. 또한 인가 서버가 검색 메타데이터 URL, JSON 웹 키 집합, 동적 클라이언트 등록, 폐기, 인트로스펙션 또는 사용자 정보 엔드포인트와 같은 추가 엔드포인트를 애플리케이션에 제공한다면 브라우저 기반 애플리케이션이 이러한 엔드포인트에도 액세스할 수 있습니다. 따라서 인가 서버는 이러한 엔드포인트에서 CORS를 지원할 책임이 있습니다.

이 명세에는 와일드카드 오리진이나 더 제한적인 구성으로 이루어질 수 있는 구체적인 CORS 정책 구현을 결정하기 위한 지침이 포함되어 있지 않습니다. CORS에는 서로 다른 보안 속성을 지닌 두 가지 동작 모드가 있다는 점에 유의하십시오. 첫 번째 모드는 브라우저가 요청을 보내고 CORS 응답 헤더를 사용하여 응답을 클라이언트 측 실행 컨텍스트에 노출할 수 있는지 결정하는 CORS 안전 목록 요청 (이전에는 단순 요청이라고 함)에 적용됩니다. 사용자 정의 요청 헤더가 있는 요청과 같은 CORS 안전 목록에 없는 요청의 경우 브라우저는 먼저 프리플라이트를 사용하여 CORS 정책을 확인합니다. 브라우저는 서버가 프리플라이트 응답에서 승인한 경우에만 실제 요청을 보냅니다.

인가 서버의 특정 구성으로 인해 프리플라이트에 대한 CORS 응답과 실제 요청에 대한 CORS 응답이 다를 수 있다는 점에 유의하십시오. 프리플라이트 중에는 인가 서버가 제공된 오리진만 검증할 수 있지만, 실제 요청 중에는 클라이언트 ID와 같은 전체 요청 데이터를 확인할 수 있습니다. 따라서 인가 서버는 프리플라이트 중에는 알려진 오리진을 승인하지만, 오리진을 해당 클라이언트에 사전 등록된 오리진 목록과 비교한 후 실제 요청을 거부할 수 있습니다.

6.3.4. 위협 분석

이 절에서는 제5절의 공격 시나리오와 결과를 다시 살펴보고 잠재적인 추가 방어 수단을 설명합니다.

6.3.4.1. 공격 시나리오 및 결과

공격자가 애플리케이션의 실행 컨텍스트에서 악성 코드를 실행할 수 있다면 다음 공격 시나리오가 관련됩니다.

가장 위험한 공격 시나리오는 새로운 토큰의 획득 및 추출입니다. 이 공격 시나리오에서 공격자는 인가 서버와만 상호작용하므로 클라이언트에서 OAuth 기능을 실제로 구현한 세부 방식은 중요하지 않습니다. 적법한 클라이언트 애플리케이션이 공격자로부터 토큰을 완전히 격리하는 방법을 찾더라도 공격자는 여전히 인가 서버로부터 토큰을 얻을 수 있습니다.

이러한 공격 시나리오는 다음 결과를 초래한다는 점에 유의하십시오.

6.3.4.2. 추가 방어 수단

이 아키텍처는 본질적으로 악성 브라우저 기반 코드에 취약하지만 애플리케이션의 보안 태세를 강화하는 데 도움이 되는 몇 가지 추가 방어 수단이 있습니다. 이러한 방어 수단 중 어느 것도 공격자가 새로운 흐름을 실행하여 토큰을 얻을 수 있도록 하는 근본적인 문제를 해결하지 못한다는 점에 유의하십시오.

6.3.4.2.1. 안전한 토큰 저장

토큰을 직접 처리할 때 애플리케이션은 액세스 토큰과 갱신 토큰을 저장하기 위해 서로 다른 저장 메커니즘을 선택할 수 있습니다. 로컬 스토리지 [WebStorage]와 같이 어디에서나 액세스할 수 있는 저장 영역은 웹 워커 [WebWorker]처럼 더 격리된 저장 영역보다 악성 JavaScript에서 액세스하기 쉽습니다. 제8절에서는 다양한 저장 메커니즘과 각각의 절충점을 자세히 설명합니다.

실용적인 구현 패턴에서는 웹 워커 [WebWorker]를 사용하여 갱신 토큰을 격리하고 리소스 서버로 요청을 보내는 애플리케이션에 액세스 토큰을 제공할 수 있습니다. 이렇게 하면 공격자가 애플리케이션의 갱신 토큰을 사용하여 새 토큰을 얻는 것을 방지할 수 있습니다.

그러나 공격자로부터 토큰을 완전히 격리하는 토큰 저장 메커니즘도 공격자가 새로운 흐름을 실행하여 새로운 토큰 집합을 얻는 것을 방지하지 못합니다(제 5.1.3절 참조).

6.3.4.2.2. 송신자 제한 토큰 사용

브라우저 기반 OAuth 클라이언트는 베어러 액세스 토큰과 베어러 갱신 토큰에서 송신자 제한 토큰으로 전환하기 위해 DPoP [RFC9449]를 구현할 수 있습니다. 이러한 구현에서는 DPoP 증명에 서명하는 데 사용되는 비공개 키를 브라우저가 처리합니다(추출할 수 없는 [CryptoKeyPair]을 [W3C.IndexedDB]를 사용하여 저장함). 따라서 DPoP를 사용하면 XSS 공격자가 애플리케이션의 토큰을 외부로 유출하는 시나리오를 효과적으로 방지할 수 있습니다(제5.1.1절 및 제5.1.2절 참조).

DPoP를 사용하더라도 공격자가 새로운 흐름을 실행하여 새 액세스 토큰(및 선택적으로 갱신 토큰)을 얻는 것은 방지하지 못합니다. 제5.1.3절을 참조하십시오. DPoP가 필수인 경우에도 공격자는 새로운 토큰 집합을 자신이 제어하는 키 쌍에 바인딩하여 송신자 제한 토큰을 외부로 유출하고, 공격자가 제어하는 키로 필요한 DPoP 증명을 계산하여 이를 사용할 수 있습니다.

6.3.4.2.3. 인가 서버에 대한 액세스 제한

공격자가 새 액세스 토큰과 선택적으로 갱신 토큰을 얻는 제5.1.3절의 시나리오는 브라우저 내부에서 인가 서버와 직접 상호작용할 수 있는 능력에 의존합니다. 이론적으로는 공격자가 인가 서버와 자동으로 상호작용하지 못하도록 하는 방어 수단으로 가장 위험한 공격 시나리오를 해결할 수 있습니다. 그러나 실제로 이러한 방어 수단은 효과가 없거나 실용적이지 않습니다.

완전성을 위해 이 BCP에서는 몇 가지 선택지를 아래에 나열합니다. 이러한 방어 수단은 실용적으로 사용할 수 있는 보안상의 이점을 제공하지 않으므로 어느 것도 권고되지 않는다는 점에 유의하십시오.

인가 서버는 iframe 내부에서 시작된 인가 요청을 차단할 수 있습니다. 이렇게 하면 제5.1.3절의 시나리오는 정확히 방지할 수 있지만 공격 시나리오를 약간만 변형해도 효과가 없습니다. 예를 들어 공격자는 팝업 창이나 팝언더 창에서 자동 흐름을 시작할 수 있습니다. 또한 브라우저 전용 OAuth 클라이언트는 일반적으로 숨겨진 iframe 기반 흐름을 사용하여 사용자의 인증 상태를 초기화하므로 이 접근 방식은 사용자 경험에 상당한 영향을 미칩니다.

인가 서버는 모든 인가 코드 흐름에서 사용자의 동의를 필수로 지정하여(제10.2절, [RFC6749]에 설명됨) 인가 코드를 발급하기 전에 사용자 상호작용을 요구할 수 있습니다. 이러한 접근 방식은 공격자가 자동 흐름을 실행하여 새로운 토큰 집합을 얻기 어렵게 합니다. 그러나 지속해서 동의를 요구하므로 사용자 경험에도 상당한 영향을 미칩니다. 따라서 이 접근 방식은 "동의 피로"를 초래하며, 사용자가 공격자가 시작한 흐름과 연결된 경우에도 무조건 동의할 가능성이 높아집니다.

6.3.4.3. 요약

요약하면 브라우저 기반 OAuth 클라이언트 애플리케이션의 아키텍처는 간단하지만 애플리케이션의 공격 표면을 크게 증가시킵니다. 공격자는 클라이언트를 하이재킹할 수 있을 뿐만 아니라 브라우저 기반 애플리케이션에서 완전한 기능을 갖춘 토큰 집합을 추출할 수도 있습니다.

이 아키텍처는 비즈니스 애플리케이션, 민감한 애플리케이션 및 개인정보를 처리하는 애플리케이션에 권고되지 않습니다.

7. 권장되지 않거나 사용 중단된 아키텍처 패턴

지난 20년 동안 클라이언트 애플리케이션과 백엔드 애플리케이션은 위협, 공격자 모델 및 현대적인 애플리케이션 보안에 관한 전반적인 이해와 함께 크게 발전했습니다. 이에 따라 업계에서 일반적으로 받아들여졌거나 OAuth 워킹 그룹에서 발표했던 과거 권고 사항은 이제 더 이상 권고되지 않는 경우가 많으며, 제안된 해결책도 흔히 예상되는 보안 요구 사항을 충족하지 못합니다.

이 절에서는 현대적인 브라우저 기반 OAuth 애플리케이션에 사용하도록 권고되지 않는 몇 가지 대체 아키텍처 패턴을 설명합니다. 또한 관련된 경우의 공격 시나리오와 결과를 조사하는 위협 분석과 함께 각 패턴을 설명합니다.

7.1. 단일 도메인 브라우저 기반 애플리케이션(OAuth를 사용하지 않음)

단순한 애플리케이션에서 세션 관리 개념을 대체하기 위해 OAuth를 사용함으로써 불필요하게 복잡해지는 경우가 너무 많습니다. 대표적인 예는 이제 서버 측 API가 지원하는 브라우저 기반 프런트엔드로 구성되는 현대적인 서버 측 모델-뷰-컨트롤러(MVC) 애플리케이션입니다.

이러한 애플리케이션에서 OpenID Connect를 사용하여 사용자 인증을 전용 제공자에게 맡기면 애플리케이션의 아키텍처와 개발을 크게 단순화할 수 있습니다. 그러나 프런트엔드와 백엔드 사이의 액세스를 관리하기 위해 OAuth를 사용할 필요는 없는 경우가 많습니다. 애플리케이션은 액세스 토큰을 사용하는 대신 서버 측 쿠키 기반 세션 상태를 사용하여 사용자의 인증 상태를 추적할 수 있습니다. 세션 쿠키를 보호하기 위한 보안 지침은 제6.1.3.2절에서 설명합니다.

OAuth를 사용하지 말라는 조언이 이 문서에 어울리지 않는 것처럼 보일 수 있지만, OAuth는 원래 API에 대한 서드 파티 또는 연합 액세스를 위해 만들어졌으므로 단일 공통 도메인 배포에서는 최선의 해결책이 아닐 수 있다는 점에 유의해야 합니다. 그렇지만 공통 도메인 아키텍처에서도 OAuth를 사용하면 다음과 같은 몇 가지 이점이 있습니다.

  • 향후 더 높은 유연성을 확보할 수 있습니다. 예를 들어 나중에 시스템에 새 도메인을 추가할 때 OAuth가 이미 마련되어 있다면 새 도메인을 위해 아키텍처를 추가로 재구성할 필요가 없습니다.

  • 통합을 위한 전용 코드를 작성하는 대신 기존 라이브러리 지원을 활용할 수 있습니다.

  • 로그인 및 다단계 인증 지원, 계정 관리와 복구를 애플리케이션 로직의 일부로 만들지 않고 OAuth 서버에서 중앙 집중식으로 처리할 수 있습니다.

  • 사용자 인증과 리소스 제공에 관한 책임을 분리할 수 있습니다.

자사 동일 도메인 시나리오의 브라우저 기반 애플리케이션에 OAuth를 사용하면 이러한 이점을 얻을 수 있으며, 앞에서 설명한 모든 아키텍처 패턴으로 구현할 수 있습니다.

7.1.1. 위협 분석

OAuth를 사용하지 않으므로 이 아키텍처 패턴은 사용자 브라우저를 통한 요청 프록시 처리라는 공격 시나리오에만 취약합니다 (제5.1.4절). 따라서 이 패턴은 클라이언트 하이재킹이라는 결과를 초래할 수 있습니다(제5.2.3절).

7.2. OAuth 암시적 권한 부여

OAuth 2.0 암시적 권한 부여 유형(제4.2절, [RFC6749]에 정의됨)은 인가 코드 교환 단계 없이 인가 서버가 인가 응답(프런트 채널)에 액세스 토큰을 발급하는 방식으로 작동합니다. 이 경우 액세스 토큰이 리디렉션 URI의 프래그먼트 부분에 반환되므로 공격자가 액세스 토큰을 가로채고 탈취할 여러 기회가 생깁니다.

암시적 권한 부여 유형은 보안 속성으로 인해 더 이상 권고되는 모범 사례가 아닙니다. 이 흐름의 사용을 효과적으로 방지하려면 인가 서버가 인가 응답에 액세스 토큰을 발급해서는 안 되며, 토큰 엔드포인트에서만 액세스 토큰을 반드시 발급해야 합니다. 브라우저 기반 클라이언트는 인가 코드 권한 부여 유형을 반드시 사용해야 하며, 액세스 토큰을 얻기 위해 암시적 권한 부여 유형을 사용해서는 안 됩니다.

7.2.1. 역사적 참고 사항

과거에는 JavaScript가 페이지를 새로 로드하지 않고도 URL의 프래그먼트 부분을 언제든 임의로 읽고 조작할 수 있었으므로 암시적 권한 부여 유형이 브라우저 기반 애플리케이션에 이점을 제공했습니다. 이는 앱이 액세스 토큰을 얻은 후 URL에서 해당 토큰을 제거하기 위해 필요했습니다. 또한 CORS가 브라우저에 널리 보급되기 전에는 암시적 권한 부여 유형이 브라우저나 서버에서 CORS 지원을 요구하지 않는 대체 흐름을 제공했습니다.

현대 브라우저에는 이제 [HTML]의 "탐색 및 세션 기록"에 설명된 세션 기록 API가 있으며, 페이지를 새로 로드하지 않고 URL의 경로와 쿼리 문자열 구성 요소를 수정할 수 있는 메커니즘을 제공합니다. 또한 CORS가 널리 지원되며 단일 페이지 애플리케이션에서 여러 용도로 자주 사용됩니다. 이는 현대적인 브라우저 기반 애플리케이션이 PKCE와 함께 OAuth 2.0 인가 코드 권한 부여 유형을 사용할 수 있음을 의미합니다. 세션 기록 API를 사용하면 페이지를 새로 로드하지 않고 쿼리 문자열에서 인가 코드를 제거할 수 있고, 토큰 엔드포인트에서 CORS를 지원하면 인가 서버가 다른 도메인에 있더라도 앱이 토큰을 얻을 수 있기 때문입니다.

7.2.2. 위협 분석

이 절에서 설명하는 아키텍처 패턴은 다음 공격 시나리오에 취약합니다.

따라서 이 패턴은 다음 결과를 초래할 수 있습니다.

7.2.3. 암시적 권한 부여에 대한 추가 공격

이미 설명한 공격 시나리오와 결과 외에도 암시적 권한 부여 유형의 사용 중단을 뒷받침하는 몇 가지 추가 공격이 있습니다. [RFC6819]와 제 4.1.2절, [RFC9700]에서 설명하는 암시적 권한 부여 유형에 대한 여러 공격에는 충분한 완화 전략이 없습니다. 다음 절에서는 암시적 권한 부여 유형을 계속 사용하면서 완화할 수 없는 구체적인 공격을 설명합니다.

7.2.3.1. 리디렉션 URI 조작

공격자가 인가 응답을 자신이 제어하는 URI로 전송되게 할 수 있다면 액세스 토큰을 포함한 인가 응답에 직접 액세스할 수 있습니다. 이 공격을 수행하는 여러 방법은 [RFC9700]에 자세히 설명되어 있습니다.

7.2.3.2. 브라우저 기록의 액세스 토큰 유출

공격자는 브라우저 기록에서 액세스 토큰을 얻을 수 있습니다. [RFC6819]에서 권고하는 대응책은 토큰에 짧은 만료 시간을 사용하고 브라우저에서 응답을 캐시하지 않도록 하는 것으로 제한됩니다. 이들 중 어느 것도 공격을 완전히 방지하지 못하며 잠재적인 피해만 줄일 수 있습니다.

또한 많은 브라우저가 이제 브라우저 기록을 클라우드 서비스 및 여러 기기와 동기화하므로 URL에서 액세스 토큰을 추출할 수 있는 공격 표면이 더욱 넓어집니다.

이에 관한 자세한 내용은 제4.3.2절, [RFC9700]에서 설명합니다.

7.2.3.3. 스크립트 조작

공격자는 기업 네트워크 등에서 브라우저의 HTTPS 연결을 가로채는 경우를 포함해 여러 수단을 통해 페이지를 수정하거나 브라우저에 스크립트를 삽입할 수 있습니다. 일반적으로 TLS 계층에 대한 공격을 방지하는 것은 기본 보안 권고 사항의 범위를 벗어나지만, 브라우저 기반 애플리케이션에서는 이러한 공격을 훨씬 쉽게 수행할 수 있습니다. 삽입된 스크립트를 통해 공격자는 페이지의 모든 것에 액세스할 수 있습니다.

애플리케이션이 액세스 토큰을 얻는 알려진 표준 방식을 사용할 때 페이지에서 실행되는 악성 스크립트의 위험이 커질 수 있습니다. 즉, 공격자는 언제든 window.location 변수를 확인하여 액세스 토큰을 찾을 수 있습니다. 이러한 위협 프로필은 인가 코드 흐름을 통해 얻은 액세스 토큰이 어디에 또는 어떻게 저장되는지 알고 개별 애플리케이션을 구체적으로 대상으로 삼는 공격자와 다릅니다.

7.2.3.4. 서드 파티 스크립트로의 액세스 토큰 유출

브라우저 기반 애플리케이션에서는 분석 도구, 충돌 보고, 소셜 미디어 "좋아요" 버튼 등의 서드 파티 스크립트를 사용하는 것이 비교적 일반적입니다. 이러한 상황에서 애플리케이션 작성자는 애플리케이션에서 실행되는 모든 코드를 완전히 파악하지 못할 수 있습니다. 액세스 토큰이 프래그먼트에 반환되면 페이지의 모든 서드 파티 스크립트에 노출됩니다.

7.2.4. 암시적 권한 부여의 단점

권고되는 인가 코드 권한 부여 유형을 사용하는 것과 비교할 때 암시적 권한 부여 유형이 불리한 몇 가지 추가 이유가 있습니다.

  • OAuth 2.0은 특정 액세스 토큰이 해당 클라이언트용인지 클라이언트가 검증할 수 있는 메커니즘을 제공하지 않습니다. 따라서 악의적인 당사자가 다른 수단을 통해 얻은 액세스 토큰을 클라이언트에 전달하면 오용 및 사칭 공격으로 이어질 수 있습니다.

  • 프런트 채널 리디렉션에서 액세스 토큰을 반환하면 리디렉션이 실패하거나 가로채질 수 있는 방법이 많으므로 인가 서버는 액세스 토큰이 실제로 애플리케이션에 도달한다고 보장할 수 없습니다.

  • 암시적 권한 부여 유형을 지원하려면 추가 코드와 더 많은 유지 관리가 필요하며 관련 보안 고려 사항을 이해해야 합니다. 인가 서버를 인가 코드 권한 부여 유형으로만 제한하면 구현의 공격 표면이 줄어듭니다.

  • 브라우저 기반 애플리케이션이 네이티브 앱으로 래핑되는 경우 [RFC8252]에서도 결국 PKCE와 함께 인가 코드 권한 부여 유형을 사용하도록 요구합니다.

7.3. 리소스 소유자 비밀번호 자격 증명 권한 부여

리소스 소유자 비밀번호 자격 증명 권한 부여는 제2.4절, [RFC9700]에 설명된 대로 사용해서는 안 됩니다. 대신 인가 코드 권한 부여 유형을 사용하고 사용자를 인가 서버로 리디렉션하면 인가 서버에서 사용자에게 피싱에 안전한 인증 옵션을 제시하고, 싱글 사인온 세션을 활용하거나 서드 파티 신원 제공자를 사용할 수 있습니다. 반면 리소스 소유자 비밀번호 자격 증명 권한 부여에는 이러한 기능을 위한 내장 메커니즘이 없으므로 사용자 정의 프로토콜로 확장해야 합니다.

이 모범 사례를 준수하려면 OAuth 또는 OpenID Connect를 사용하는 브라우저 기반 애플리케이션은 이 문서에 설명된 대로 리디렉션 기반 흐름(예: OAuth 인가 코드 권한 부여 유형)을 반드시 사용해야 합니다.

7.4. 서비스 워커에서 OAuth 흐름 처리

공격자가 기존 토큰을 추출하거나 새로운 토큰 집합을 얻는 능력을 제한하기 위해 과거에 서비스 워커 [W3C.service-workers]를 사용하는 패턴이 제안되었습니다. 이 패턴에서 애플리케이션은 로드된 후 가장 먼저 서비스 워커를 등록합니다. 서비스 워커는 토큰을 얻기 위한 인가 코드 흐름을 실행하고, 리소스 서버로 나가는 요청에 적절한 액세스 토큰을 추가할 책임을 맡습니다. 또한 서비스 워커는 클라이언트 애플리케이션의 코드가 인가 서버의 엔드포인트를 직접 호출하지 못하도록 차단합니다. 이 제한은 "새로운 토큰의 획득 및 추출" 공격 시나리오(제5.1.3절)에 대응하기 위한 것입니다.

아래에 포함된 시퀀스 다이어그램은 클라이언트, 서비스 워커, 인가 서버 및 리소스 서버 사이의 상호작용을 보여 줍니다.

서비스 리소스 인가 사용자 애플리케이션 워커 서버 서버 탐색 /authorize 인가 코드가 포함된 리디렉션 < - - - - - - - - - - - - - - - - - 인가 코드가 포함된 토큰 요청 /token < - - - - - - - - - - - - - - - - - 리소스 요청 리소스 요청 액세스 토큰 포함 사용자 애플리케이션 서비스 리소스 인가 워커 서버 서버
그림 4: OAuth 2.0 서비스 워커 패턴

이 패턴은 브라우저에서 실행되는 애플리케이션에 토큰을 노출하지 않는다는 점에 유의하십시오. 서비스 워커는 격리된 실행 환경에서 실행되므로 공유 메모리가 없고 클라이언트 애플리케이션이 서비스 워커의 실행에 영향을 줄 방법도 없습니다.

7.4.1. 위협 분석

이 절에서 설명하는 아키텍처 패턴은 다음 공격 시나리오에 취약합니다.

따라서 이 패턴은 다음 결과를 초래할 수 있습니다.

7.4.1.1. 서비스 워커 공격

서비스 워커 사용이 제공하는 유망해 보이는 보안 이점의 한계는 더 자세히 논의할 필요가 있습니다. 관련 공격 시나리오(제5.1절)로부터 애플리케이션을 완전히 보호하려면 서비스 워커가 다음 두 가지 보안 요구 사항을 충족해야 합니다.

  1. 공격자가 토큰을 외부로 유출하지 못하도록 합니다.

  2. 공격자가 새로운 토큰 집합을 얻지 못하도록 합니다.

서비스 워커가 등록되면 인가 코드 흐름을 실행하여 토큰을 얻습니다. 서비스 워커는 자체적으로 격리된 실행 환경에서 토큰을 추적하므로 잠재적인 악성 코드를 포함한 모든 애플리케이션 코드가 토큰에 접근할 수 없습니다. 따라서 서비스 워커는 토큰 외부 유출을 방지한다는 첫 번째 요구 사항을 충족합니다. 이는 본질적으로 제5.1절에서 설명한 처음 두 공격 시나리오를 무력화합니다.

두 번째 보안 요구 사항을 충족하려면 서비스 워커가 적법한 애플리케이션을 제어하는 공격자가 새로운 인가 코드 권한 부여를 실행할 수 없도록 보장해야 합니다. 이 공격은 제 5.1.3절에서 설명합니다. 서비스 워커의 특성상 등록된 서비스 워커는 프레임이나 새 창에서 발생하더라도 이러한 새로운 흐름을 시작하는 모든 외부 요청을 차단할 수 있습니다.

그러나 애플리케이션 내부에서 실행되는 악성 코드는 이 서비스 워커의 등록을 해제할 수 있습니다. 서비스 워커의 등록을 해제하면 애플리케이션 기능에 상당한 영향을 미칠 수 있으므로 브라우저가 가볍게 처리하는 작업은 아닙니다. 따라서 등록이 해제된 서비스 워커는 그 상태로 표시되지만 현재 실행 중인 모든 인스턴스는 해당 브라우징 컨텍스트가 종료될 때까지 활성 상태를 유지합니다(예: 탭 또는 창을 닫는 경우). 따라서 공격자가 서비스 워커의 등록을 해제하더라도 서비스 워커는 활성 상태를 유지하며 공격자가 인가 서버에 접근하는 것을 계속 방지할 수 있습니다.

서비스 워커의 등록을 해제하면 새로운 브라우징 컨텍스트를 열 때 서비스 워커가 존재하지 않는다는 결과가 발생합니다. 따라서 공격자가 먼저 서비스 워커의 등록을 해제한 다음 프레임에서 새 흐름을 시작하면 프레임의 브라우징 컨텍스트와 연결된 서비스 워커가 없습니다. 이에 따라 공격자는 자체적인 새로운 인가 코드 권한 부여를 실행하고 프레임의 URL에서 인가 코드를 추출한 다음 토큰으로 교환할 수 있습니다. 본질적으로 서비스 워커는 두 번째 보안 요구 사항을 충족하지 못하므로 공격자가 새로운 토큰 집합을 얻는 시나리오(제5.1.3절)에 취약합니다.

이러한 단점과 서비스 워커를 등록하고 유지 관리하는 데 따르는 상당한 복잡성 때문에 이 패턴은 권고되지 않습니다.

마지막으로 서비스 워커 사용 자체는 애플리케이션의 공격 표면을 증가시키지 않는다는 점에 유의하십시오. 실제로 서비스 워커는 기존 애플리케이션이 나가는 요청에 OAuth 액세스 토큰을 포함하도록 지원 기능을 추가하는 데 자주 사용됩니다. 이러한 시나리오의 서비스 워커는 애플리케이션의 보안 속성을 변경하지 않으며 단지 애플리케이션의 개발과 유지 관리를 단순화할 뿐입니다.

8. 브라우저의 토큰 저장

브라우저 기반 애플리케이션이 OAuth 액세스 토큰 또는 갱신 토큰을 직접 처리하는 경우, 토큰을 일시적으로 또는 영구적으로 저장할 책임을 지게 됩니다. 따라서 애플리케이션은 토큰을 관리하는 방법(예: 메모리 내 저장소와 영구 저장소 중 선택)과 기본 애플리케이션 코드로부터 토큰을 추가로 격리하기 위해 취할 조치를 결정해야 합니다. 이 절에서는 몇 가지 저장 메커니즘과 그 속성을 설명합니다. 이러한 권고 사항은 OAuth 토큰의 고유한 속성을 고려하며, 그중 일부는 일반적인 브라우저 보안 권고 사항과 겹칠 수 있습니다.

브라우저 기반 토큰 저장 솔루션의 보안 속성을 논할 때는 공격자가 브라우저 기반 애플리케이션을 침해했을 때 보유하게 되는 역량을 이해하는 것이 중요합니다. 앞선 논의와 마찬가지로 다음 두 가지 주요 공격 시나리오를 고려해야 합니다.

  1. 공격자가 저장소에서 토큰을 획득합니다.

  2. 공격자가 공급자(예: 인가 서버 또는 토큰 중개 백엔드)로부터 토큰을 획득합니다.

공격자의 코드는 합법적인 애플리케이션의 코드와 구별할 수 없게 되므로, 공격자는 언제나 합법적인 애플리케이션 코드와 정확히 같은 방식으로 공급자에게 토큰을 요청할 수 있습니다. 따라서 완전히 격리된 토큰 저장 솔루션조차 공격자가 공급자에게 토큰을 요청하는 두 번째 위협의 위험을 해결할 수는 없습니다.

다만 브라우저 기반 저장 솔루션마다 보안 속성이 다르므로, 공격자가 저장소에서 기존 토큰을 획득할 수 있는 정도도 달라집니다.

8.1. 쿠키

브라우저 쿠키는 저장 메커니즘인 동시에 전송 메커니즘입니다. 브라우저는 해당 요청 및 응답 헤더를 통해 두 메커니즘을 모두 자동으로 지원합니다. 그 결과 쿠키가 브라우저에 저장되며, 쿠키의 도메인, 경로 또는 기타 속성과 일치하는 경우 외부로 전송되는 요청에 쿠키가 자동으로 포함됩니다.

브라우저는 헤더를 통한 쿠키 제어 외에도 쿠키를 가져오고 설정할 수 있는 JavaScript 쿠키 API를 제공합니다. 이 쿠키 API는 흔히 브라우저에 데이터를 저장하는 간편한 방법으로 오해됩니다. 이러한 시나리오에서 JavaScript 코드는 나중에 API 호출의 Authorization 헤더에 포함하기 위해 토큰을 쿠키에 저장합니다. 그러나 쿠키는 브라우저 기반 애플리케이션의 도메인과 연결되므로, 브라우저는 이 도메인에서 실행되는 서버에 요청할 때도 토큰이 포함된 쿠키를 전송합니다. 그러한 요청의 한 가지 예는 이전에 애플리케이션을 방문한 후 브라우저가 애플리케이션을 로드하는 경우입니다(그림 3의 (A)).

JavaScript 기반 저장소로 쿠키를 사용할 때 발생하는 이러한 의도하지 않은 부작용 때문에 이 관행은 권장되지 않습니다.

이 관행은 쿠키가 JavaScript에서 접근할 수 없고 백엔드로 전송되도록 의도된 BFF의 쿠키 사용(제 6.1.3.2절에서 설명)과는 다릅니다.

8.2. 서비스 워커의 토큰 저장

서비스 워커 [W3C.service-workers]는 토큰을 관리할 수 있는 완전히 격리된 환경을 제공합니다. 이러한 토큰은 클라이언트 애플리케이션에서 접근할 수 없으므로 유출로부터 효과적으로 보호됩니다. 이러한 토큰의 보안을 보장하려면 서비스 워커가 토큰을 애플리케이션과 공유해서는 안 됩니다. 따라서 애플리케이션이 토큰을 사용하는 작업을 수행하려면 서비스 워커에 해당 작업을 수행하고 결과를 반환하도록 요청해야 합니다.

애플리케이션의 실행 컨텍스트로부터 토큰을 격리하려는 경우 서비스 워커는 기본 창과 공유되는 영구 저장소 API에 토큰을 저장해서는 안 됩니다. 예를 들어 현재 IndexedDB 저장소는 브라우징 컨텍스트와 서비스 워커 사이에서 공유되므로, 기본 창에서 접근할 수 없어야 하는 데이터를 서비스 워커가 영구적으로 저장하기에 적합하지 않습니다. 따라서 현재 서비스 워커는 격리된 영구 저장 영역에 접근할 수 없습니다.

앞서 설명했듯이 서비스 워커를 사용해도 공격자가 새로운 토큰 세트를 획득하는 것을 막을 수는 없습니다. 마찬가지로 애플리케이션이 인가 서버에서 토큰을 획득하여 이후 관리를 위해 서비스 워커에 전달하는 역할을 담당한다면, 공격자도 합법적인 애플리케이션과 동일한 작업을 수행하여 이러한 토큰을 획득할 수 있습니다.

8.3. 웹 워커의 토큰 저장

애플리케이션은 웹 워커 [WebWorker]를 사용할 수 있으며, 이는 서비스 워커를 사용하는 앞선 시나리오와 거의 동일한 결과를 가져옵니다. 서비스 워커와 웹 워커의 차이점은 접근 수준과 런타임 속성입니다. 서비스 워커는 외부로 전송되는 요청을 가로채고 수정할 수 있지만, 웹 워커는 단지 백그라운드 작업을 실행하는 수단입니다. 웹 워커는 일시적이며 브라우징 컨텍스트가 닫히면 사라지는 반면, 서비스 워커는 브라우저에 등록되는 영구적인 서비스입니다.

웹 워커를 사용하는 경우의 보안 속성은 서비스 워커를 사용하는 경우와 동일합니다. 토큰이 애플리케이션에 노출되면 취약해집니다. 토큰을 사용해야 할 때는 해당 토큰에 의존하는 작업을 웹 워커가 수행해야 합니다.

갱신 토큰을 격리하는 일반적인 방법 중 하나는 웹 워커를 사용하는 것입니다. 이러한 시나리오에서 애플리케이션은 웹 워커에서 인가 코드 흐름을 시작합니다. 리디렉션에서 받은 인가 코드는 웹 워커로 전달되고, 웹 워커는 이를 토큰으로 교환합니다. 웹 워커는 갱신 토큰을 메모리에 보관하고 액세스 토큰을 기본 애플리케이션에 전송합니다. 기본 애플리케이션은 필요에 따라 액세스 토큰을 사용합니다. 애플리케이션이 갱신 토큰 흐름을 실행해야 할 때 웹 워커에 이를 요청하며, 이후 애플리케이션은 새로운 액세스 토큰을 획득합니다.

이 시나리오에서는 애플리케이션 자체의 갱신 토큰이 유출로부터 효과적으로 보호되지만 액세스 토큰은 그렇지 않습니다. 또한 공격자가 새로운 인가 코드 흐름을 실행하여 자체 토큰을 획득하는 것을 막을 수는 없습니다(제5.1.3절 참조).

8.4. 메모리 내 토큰 저장

또 다른 선택지는 영구 저장소를 사용하지 않고 토큰을 메모리에 보관하는 것입니다. 이렇게 하면 토큰의 노출이 현재 실행 컨텍스트로만 제한되지만, 페이지를 다시 로드할 때까지 토큰을 유지할 수 없다는 단점이 있습니다.

JavaScript 실행 환경에서는 클로저 변수를 사용하여 토큰에 대한 직접 접근을 효과적으로 차단함으로써 메모리 내 토큰 저장의 보안을 더욱 강화할 수 있습니다. 클로저를 사용하면 리소스 서버에 요청을 보내는 함수와 같이 클로저 내부에 미리 정의된 함수만 토큰에 접근할 수 있습니다.

클로저는 단순하고 격리된 환경에서는 잘 작동하지만, 브라우저 실행 환경과 같은 복잡한 환경에서는 안전하게 보호하기 어렵습니다. 예를 들어 클로저는 작업을 실행하기 위해 toString이나 네트워킹 API와 같은 다양한 외부 함수에 의존합니다. 공격자는 프로토타입 오염을 사용하여 이러한 함수를 악성 버전으로 교체함으로써 클로저의 이후 작업이 해당 악성 버전을 사용하도록 만들 수 있습니다. 악성 함수 내부에서 공격자는 함수 인수에 접근할 수 있으며, 이로 인해 클로저 내부의 토큰이 공격자에게 노출될 수 있습니다.

8.5. 영구 토큰 저장

이 문서 작성 시점에 브라우저에서 사용할 수 있는 영구 저장소 API는 localStorage [WebStorage], sessionStorage [WebStorage] 및 IndexedDB [W3C.IndexedDB]입니다.

localStorage는 페이지를 다시 로드해도 유지되며 모든 탭에서 공유됩니다. 이 저장소는 전체 오리진에서 접근할 수 있고 장기간 유지됩니다. 공격자는 동일한 오리진 내에서 코드를 실행하므로 localStorage의 내용을 읽을 수 있습니다. 따라서 localStorage는 악성 JavaScript의 무단 접근을 방지하지 못합니다. 또한 localStorage는 동기식 API이므로 작업이 완료될 때까지 다른 JavaScript의 실행을 차단합니다.

sessionStorage는 localStorage와 유사하지만 sessionStorage의 수명이 브라우저 탭의 수명과 연결됩니다. 또한 sessionStorage는 동일한 오리진의 페이지를 연 여러 탭 간에 공유되지 않으므로 sessionStorage에 있는 토큰의 노출이 약간 줄어듭니다.

IndexedDB는 localStorage와 같은 영구 저장 메커니즘이지만 여러 탭뿐 아니라 브라우징 컨텍스트와 서비스 워커 간에도 공유됩니다. 또한 IndexedDB는 비동기식 API이므로 동기식 localStorage API보다 선호됩니다.

이러한 패턴의 주요 차이점은 데이터의 노출 범위이지만, 공격자가 애플리케이션의 실행 환경에서 악성 코드를 실행할 수 있는 경우 어느 선택지도 토큰 유출을 완전히 완화할 수는 없습니다.

8.6. 브라우저 저장소 API에 대한 파일시스템 고려 사항

어떤 경우든 이 문서 작성 시점에는 브라우저 저장소가 저장 상태에서 암호화된다는 보장이 없습니다. 이러한 동작으로 인해 디스크의 파일을 읽을 수 있는 공격자에게 토큰이 노출될 수 있습니다. 이러한 공격에는 브라우저 기반 애플리케이션의 범위를 훨씬 넘어서는 역량이 필요하지만, 이 주제는 현대적인 애플리케이션에 대한 중요한 공격 벡터를 보여 줍니다. 사용자의 시스템을 탐색하고 브라우저 프로필에서 가치가 높은 토큰과 세션 쿠키를 찾도록 특별히 제작된 악성 소프트웨어가 점점 늘어나고 있으며, 이는 계정 탈취 공격으로 이어집니다.

브라우저 기반 애플리케이션은 이러한 공격 자체를 완화할 수 없지만, 암호화를 사용해 데이터 기밀성을 보장함으로써 공격의 결과를 완화할 수 있습니다. [W3C.WebCryptoAPI]는 JavaScript 코드가 비밀 키를 생성할 수 있는 메커니즘과 해당 키를 내보낼 수 없도록 하는 선택지를 제공합니다. JavaScript 애플리케이션은 이 API를 사용하여 토큰을 저장하기 전에 암호화하고, 사용할 때 복호화할 수 있습니다. 그러나 [W3C.WebCryptoAPI] 명세는 키를 브라우저 코드로 내보낼 수 없도록 보장할 뿐, 운영 체제에서 사용하는 키의 기반 저장 방식에는 어떠한 요구 사항도 부과하지 않습니다. 따라서 내보낼 수 없는 키를 기반 파일시스템으로부터의 유출을 방지하는 수단으로 신뢰할 수는 없습니다.

파일시스템에서 토큰이 유출되는 것을 방지하려면 암호화 키를 원격 서버와 같이 파일시스템이 아닌 다른 위치에 저장해야 합니다. 이는 순수한 브라우저 기반 앱에 새로운 복잡성을 초래하며 이 문서의 범위를 벗어납니다.

9. 보안 고려 사항

9.1. 토큰의 권한 축소

OAuth 환경의 일반적인 보안 모범 사례는 액세스 토큰과 연결된 권한을 최소화하는 것입니다. 이 모범 사례는 본 명세에서 설명하는 모든 아키텍처에 적용됩니다. 구체적으로 다음 고려 사항을 통해 액세스 토큰의 권한을 최소화할 수 있습니다.

  • 액세스 토큰의 수명을 줄이고 액세스 토큰 갱신에는 갱신 토큰을 사용합니다

  • 액세스 토큰과 연결된 범위 또는 권한을 축소합니다

  • [RFC8707]에 설명된 확장을 사용하여 액세스 토큰을 단일 리소스로 제한합니다

OpenID Connect를 사용하는 경우 ID 토큰의 클레임을 통해 민감한 정보가 공개되지 않도록 하는 것이 중요합니다. 인가 서버는 클라이언트에서 사용하지 않는 ID 토큰 클레임을 포함해서는 안 됩니다.

9.2. 송신자 제한 토큰

이 문서 전반에서 설명했듯이 송신자 제한 토큰을 사용하더라도 브라우저 전용 OAuth 클라이언트의 보안 한계가 해결되지는 않습니다. 그러나 토큰 중개 백엔드(제6.2절) 또는 브라우저 전용 OAuth 클라이언트(제6.3절)가 제공하는 보안 수준이 해당 사용 사례에 충분한 경우, 송신자 제한 토큰을 사용하여 액세스 토큰과 갱신 토큰 모두의 보안을 강화할 수 있습니다. 브라우저 기반 애플리케이션에서 사용할 수 있는 방식으로 송신자 제한 토큰을 구현하는 방법 중 하나는 DPoP [RFC9449]입니다.

송신자 제한 토큰을 사용할 때 OAuth 클라이언트는 토큰을 사용하기 위해 개인 키의 소유를 증명해야 하므로 토큰만으로는 사용할 수 없습니다. 송신자 제한 토큰이 도난당하더라도 공격자는 토큰을 직접 사용할 수 없으며, 개인 키도 훔쳐야 합니다. 본질적으로 송신자 제한 토큰을 사용하면 토큰을 안전하게 저장해야 하는 과제가 개인 키를 안전하게 저장해야 하는 과제로 전환된다고 할 수 있습니다. 애플리케이션은 [W3C.WebCryptoAPI]로 생성한 키처럼 내보낼 수 없는 개인 키를 사용하는 것이 이상적입니다. 브라우저 저장소의 암호화되지 않은 토큰이 내보낼 수 없는 개인 키로 보호되는 경우 XSS 공격으로는 키를 추출할 수 없으므로 공격자가 토큰을 사용할 수 없습니다.

애플리케이션이 내보낼 수 없는 키를 생성하는 API를 사용할 수 없다면 자체 실행 컨텍스트로부터 개인 키를 격리하기 위한 조치를 취해야 합니다. 이를 위한 기법은 제8절에서 설명한 안전한 토큰 저장 메커니즘의 사용과 유사합니다.

내보낼 수 없는 키는 JavaScript 컨텍스트 내부에서 유출되지 않도록 보호되지만, 기반 개인 키가 파일시스템에서 유출되는 것은 여전히 잠재적인 공격 벡터입니다. 이 문서 작성 시점에 [W3C.WebCryptoAPI]는 내보낼 수 없는 키가 실제로 신뢰할 수 있는 플랫폼 모듈(TPM)에 의해 보호되거나 디스크에 암호화된 형태로 저장된다고 보장하지 않습니다. 공격자가 예를 들어 악성 소프트웨어를 통해 사용자 시스템의 파일시스템에 접근할 수 있다면, 기반 파일시스템에서 내보낼 수 없는 키를 유출하는 것이 여전히 가능할 수 있습니다. 이로 인해 잠재적인 공격 벡터는 사실상 세션 하이재킹 공격과 동일해집니다.

9.3. 인가 서버 혼동 공격 완화

인가 서버 혼동 공격은 둘 이상의 인가 서버를 지원하는 모든 클라이언트에 심각한 위협이 됩니다. 섹션 4.4 of [RFC9700]에서는 혼동 공격 및 위에서 언급한 대응책에 대한 추가 세부 정보를 제공합니다.

9.4. 오리진을 사용하여 애플리케이션 격리하기

웹의 많은 보안 메커니즘은 오리진에 의존하며, 오리진은 <scheme, hostname, port> 삼중항으로 정의됩니다. 예를 들어 브라우저는 서로 다른 오리진을 가진 탐색 컨텍스트를 자동으로 격리하고, 리소스를 특정 오리진으로 제한하며, 나가는 교차 오리진 요청에 CORS 제한을 적용합니다.

따라서 하나의 오리진에 둘 이상의 애플리케이션을 배포하지 않는 것이 모범 사례로 간주됩니다. 하나의 오리진에 단일 애플리케이션만 배포하는 아키텍처는 이러한 브라우저 제한을 활용하여 애플리케이션의 보안을 강화할 수 있습니다. 또한 애플리케이션마다 하나의 오리진을 사용하면 CORS, CSP 등의 보안 조치를 더 쉽게 구성하고 배포할 수 있습니다.

10. IANA 고려 사항

이 문서에는 IANA 조치가 없습니다.

11. 참고 문헌

11.1. 규범적 참고 문헌

[Fetch]
WHATWG, "Fetch", WHATWG 현행 표준, <https://fetch.spec.whatwg.org/>. 커밋 스냅샷: <https://fetch.spec.whatwg.org/commit-snapshots/d9c7b6d31067a7eb13110950ef67db54c6362cfa/>.
[RFC2119]
Bradner, S., "요구 수준을 나타내기 위해 RFC에서 사용하는 키워드", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC6749]
Hardt, D., 편집자, "OAuth 2.0 인가 프레임워크", RFC 6749, DOI 10.17487/RFC6749, , <https://www.rfc-editor.org/info/rfc6749>.
[RFC6750]
Jones, M. 및 D. Hardt, "OAuth 2.0 인가 프레임워크: Bearer 토큰 사용", RFC 6750, DOI 10.17487/RFC6750, , <https://www.rfc-editor.org/info/rfc6750>.
[RFC7636]
Sakimura, N., 편집자, Bradley, J., 및 N. Agarwal, "OAuth 공개 클라이언트의 코드 교환을 위한 Proof Key", RFC 7636, DOI 10.17487/RFC7636, , <https://www.rfc-editor.org/info/rfc7636>.
[RFC8174]
Leiba, B., "RFC 2119 키워드에서 대문자와 소문자의 모호성", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8252]
Denniss, W. 및 J. Bradley, "네이티브 앱을 위한 OAuth 2.0", BCP 212, RFC 8252, DOI 10.17487/RFC8252, , <https://www.rfc-editor.org/info/rfc8252>.
[RFC8707]
Campbell, B., Bradley, J., 및 H. Tschofenig, "OAuth 2.0을 위한 리소스 표시자", RFC 8707, DOI 10.17487/RFC8707, , <https://www.rfc-editor.org/info/rfc8707>.
[RFC9449]
Fett, D., Campbell, B., Bradley, J., Lodderstedt, T., Jones, M., 및 D. Waite, "OAuth 2.0 소유 증명 시연(DPoP)", RFC 9449, DOI 10.17487/RFC9449, , <https://www.rfc-editor.org/info/rfc9449>.
[RFC9700]
Lodderstedt, T., Bradley, J., Labunets, A., 및 D. Fett, "OAuth 2.0 보안을 위한 현재 모범 사례", BCP 240, RFC 9700, DOI 10.17487/RFC9700, , <https://www.rfc-editor.org/info/rfc9700>.
[W3C.service-workers]
Chintala, M., 편집자 및 Y. Yanagisawa, 편집자, "Service Workers Nightly", W3C 권고안, , <https://www.w3.org/TR/2026/CRD-service-workers-20260126/>. 최신 버전은 <https://www.w3.org/TR/service-workers/>에서 확인할 수 있습니다.
[WebMessaging]
WHATWG, "HTML - 문서 간 메시징", WHATWG 현행 표준, <https://html.spec.whatwg.org/#web-messaging>. 커밋 스냅샷: <https://html.spec.whatwg.org/commit-snapshots/b4b94b1db80aa55a143e1f74f6ad9d4a185f2333/#web-messaging>.

11.2. 정보성 참고 문헌

[COOKIES]
van Kesteren, A. 및 J. Hofmann, "쿠키: HTTP 상태 관리 메커니즘", 진행 중인 작업, 인터넷 초안, draft-ietf-httpbis-layered-cookies-02, , <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-layered-cookies-02>.
[CryptoKeyPair]
Mozilla Developer Network, "CryptoKeyPair", <https://developer.mozilla.org/en-US/docs/Web/API/CryptoKeyPair>.
[HTML]
WHATWG, "HTML", WHATWG 현행 표준, <https://html.spec.whatwg.org/>. 커밋 스냅샷: <https://html.spec.whatwg.org/commit-snapshots/b4b94b1db80aa55a143e1f74f6ad9d4a185f2333/>.
[OpenID]
Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., 및 C. Mortimore, "정오표 세트 2를 반영한 OpenID Connect Core 1.0", , <https://openid.net/specs/openid-connect-core-1_0-errata2.html>.
[OWASPCheatSheet]
"OWASP 치트 시트", <https://cheatsheetseries.owasp.org/>.
[RFC6265bis]
Bingler, S., 편집자, West, M., 편집자, 및 J. Wilander, 편집자, "쿠키: HTTP 상태 관리 메커니즘", 진행 중인 작업, 인터넷 초안, draft-ietf-httpbis-rfc6265bis-22, , <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22>.
[RFC6819]
Lodderstedt, T., 편집자, McGloin, M., 및 P. Hunt, "OAuth 2.0 위협 모델 및 보안 고려 사항", RFC 6819, DOI 10.17487/RFC6819, , <https://www.rfc-editor.org/info/rfc6819>.
[SessionFixation]
"세션 고정", <https://owasp.org/www-community/attacks/Session_fixation>.
[Site]
Mozilla Developer Network, "사이트", <https://developer.mozilla.org/en-US/docs/Glossary/Site>.
[W3C.CSP3]
West, M., 편집자 및 A. Sartori, 편집자, "콘텐츠 보안 정책 레벨 3", W3C 작업 초안, , <https://www.w3.org/TR/2026/WD-CSP3-20260505/>. 최신 버전은 <https://www.w3.org/TR/CSP3/>에서 확인할 수 있습니다.
[W3C.IndexedDB]
Becker, S., 편집자, "Indexed Database API 3.0", W3C 작업 초안, , <https://www.w3.org/TR/2025/WD-IndexedDB-3-20250813/>. 최신 버전은 <https://www.w3.org/TR/IndexedDB/>에서 확인할 수 있습니다.
[W3C.SRI]
Braun, F., 편집자, "하위 리소스 무결성", W3C 작업 초안, , <https://www.w3.org/TR/2025/WD-sri-2-20250710/>. 최신 버전은 <https://www.w3.org/TR/sri-2/>에서 확인할 수 있습니다.
[W3C.wasm-core-2]
Rossberg, A., 편집자, "WebAssembly 코어 명세", W3C 후보 권고안 초안, , <https://www.w3.org/TR/2026/CRD-wasm-core-2-20260210/>. 최신 버전은 <https://www.w3.org/TR/wasm-core-2/>에서 확인할 수 있습니다.
[W3C.WebCryptoAPI]
Huigens, D., 편집자, "웹 암호화 레벨 2", W3C 최초 공개 작업 초안, , <https://www.w3.org/TR/2025/WD-webcrypto-2-20250422/>. 최신 버전은 <https://www.w3.org/TR/webcrypto-2/>에서 확인할 수 있습니다.
[WebStorage]
WHATWG, "HTML - 웹 스토리지", WHATWG 현행 표준, <https://html.spec.whatwg.org/#webstorage>. <https://html.spec.whatwg.org/commit-snapshots/b4b94b1db80aa55a143e1f74f6ad9d4a185f2333/#webstorage>.
[WebWorker]
WHATWG, "HTML - 웹 워커", WHATWG 현행 표준, <https://html.spec.whatwg.org/#workers>. 커밋 스냅샷: <https://html.spec.whatwg.org/commit-snapshots/b4b94b1db80aa55a143e1f74f6ad9d4a185f2333/#workers>.

감사의 말

저자들은 William Denniss 및 John Bradley의 작업에 감사를 표합니다. 이들의 네이티브 애플리케이션에 대한 권고는 브라우저 기반 애플리케이션의 많은 모범 사례에 영향을 주었습니다. 저자들은 또한 Hannes Tschofenig 및 Torsten Lodderstedt, 이 BCP가 처음 제안된 Internet Identity Workshop 27 세션의 참석자들, 그리고 최종 명세를 형성하는 데 기여한 아이디어, 피드백 및 문구를 제공해 주신 다음 분들께도 감사드립니다.

Andy Barlow, Andy Newton, Annabelle Backman, Brian Campbell, Brock Allen, Christian Mainka, Damien Bowden, Daniel Fett, Deb Cooley, Elar Lang, Emmanuel Gautier, Erik Kline, Éric Vyncke, Eva Sarafianou, Filip Skokan, George Fletcher, Hannes Tschofenig, Janak Amarasena, John Bradley, Joseph Heenan, Justin Richer, Karl McGuinness, Karsten Meyer zu Selhausen, Leo Tohill, Louis Jannett, Marc Blanchet, Martin Thomson, Matthew Bocci, Mike Bishop, Mike Jones, Mohamed Boucadair, Orie Steele, Qin Wu, Rifaat Shekh-Yusef, Roman Danyliw, Sean Kelleher, Thomas Broyer, Thomas Fossati, Tomek Stojecki, Torsten Lodderstedt, Vittorio Bertocci, Watson Ladd, William Duncan, 그리고 Yannick Majoros.

저자 주소

Aaron Parecki
Okta
Philippe De Ryck
Pragmatic Web Security
David Waite
Ping Identity