인터넷 초안 브라우저 기반 애플리케이션을 위한 OAuth 2.0 2025년 12월
Parecki, et al. 2026년 6월 7일 만료 [페이지]
작업 그룹:
웹 권한 부여 프로토콜
인터넷 초안:
draft-ietf-oauth-browser-based-apps-26
게시일:
의도된 상태:
현재 모범 사례
만료일:
저자:
A. Parecki
Okta
P. De Ryck
Pragmatic Web Security
D. Waite
Ping Identity

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

초록

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

논의 장소

이 참고 사항은 RFC로 게시되기 전에 제거될 예정이다.

이 문서에 대한 논의는 Web Authorization Protocol Working Group 메일링 리스트(oauth@ietf.org)에서 이루어지며, https://mailarchive.ietf.org/arch/browse/oauth/에 보관된다.

이 초안의 소스와 이슈 트래커는 https://github.com/oauth-wg/oauth-browser-based-apps에서 확인할 수 있다.

이 메모의 상태

이 인터넷 초안은 BCP 78 및 BCP 79의 규정에 완전히 부합하여 제출되었다.

인터넷 초안은 Internet Engineering Task Force(IETF)의 작업 문서이다. 다른 그룹들도 작업 문서를 인터넷 초안으로 배포할 수 있음에 유의해야 한다. 현재 인터넷 초안 목록은 https://datatracker.ietf.org/drafts/current/에 있다.

인터넷 초안은 최대 6개월 동안 유효한 초안 문서이며, 언제든지 다른 문서로 업데이트, 대체 또는 폐기될 수 있다. 인터넷 초안을 참고 자료로 사용하거나 "진행 중인 작업" 이외의 방식으로 인용하는 것은 적절하지 않다.

이 인터넷 초안은 2026년 6월 7일에 만료된다.

목차

1. 소개

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

이 문서는 OAuth 클라이언트로 동작하는 JavaScript 프런트엔드 애플리케이션에 초점을 둔다 ([RFC6749]의 1.1절에 정의됨). 이러한 애플리케이션은 접근 토큰과 선택적으로 갱신 토큰을 얻기 위해 권한 부여 서버([RFC6749]의 1.1절)와 상호 작용한다. 클라이언트는 접근 토큰을 사용해 리소스 서버의 보호된 리소스([RFC6749]의 1.1절)에 접근한다. OAuth를 사용할 때 클라이언트, 권한 부여 서버, 리소스 서버는 각각이 같은 주체에 의해 소유되거나 운영되는지와 관계없이 모두 독립적인 당사자로 간주된다.

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

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

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

이러한 권고 사항 중 많은 부분은 브라우저 기반 애플리케이션도 해당 권고 사항을 따를 것으로 예상되므로, OAuth 2.0 보안을 위한 Best Current Practice [RFC9700]에서 파생되었다. 이 문서는 [RFC9700]에 제시된 여러 권고 사항을 확장하고 추가로 제한한다.

2. 표기 규칙

이 문서에서 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", "OPTIONAL"이라는 핵심 단어들은 여기에 표시된 것처럼 모두 대문자로 나타나는 경우에만 BCP 14 [RFC2119] [RFC8174]에 설명된 대로 해석되어야 한다.

3. 용어

이 명세는 OAuth 2.0 [RFC6749]에서 정의한 "접근 토큰", "권한 부여 엔드포인트", "권한 부여 부여", "권한 부여 서버", "클라이언트", "클라이언트 식별자"(클라이언트 ID), "보호된 리소스", "갱신 토큰", "리소스 소유자", "리소스 서버", "토큰 엔드포인트"라는 용어와, [RFC6750]에서 정의한 "bearer token"을 사용한다.

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

"OAuth":

이 문서에서 "OAuth"는 OAuth 2.0, [RFC6749][RFC6750]을 가리킨다.

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

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

이 문서는 런타임 환경에서 브라우저에 의해 실행되는 브라우저 기반 애플리케이션의 보안을 논의한다. 대부분의 시나리오에서 이러한 애플리케이션은 JavaScript (JS) 실행 환경에서 실행되는 JavaScript 애플리케이션이다. 이 시나리오의 보편성을 고려하여, 이 문서는 "JavaScript"라는 용어를 브라우저 안의 애플리케이션 런타임에서 코드 실행을 허용하는 모든 메커니즘을 가리키는 데 사용한다. 이 문서의 권고 사항과 고려 사항은 JavaScript 언어나 그 런타임에만 독점적으로 연결되는 것이 아니라, Web Assembly([W3C.wasm-core-2])와 같은 브라우저 내의 다른 언어 및 런타임 환경에도 적용된다.

"PKCE":

Proof Key for Code Exchange (PKCE) [RFC7636]는 OAuth 권한 부여 코드에 대한 여러 공격을 방지하기 위한 메커니즘이다.

"DPoP":

OAuth 2.0 Demonstrating of Proof of Possession (DPoP) [RFC9449]는 접근 토큰이 발급된 클라이언트에 의해서만 사용되도록 제한하는 메커니즘이다.

"CORS":

Cross-Origin Resource Sharing [Fetch]는 브라우저의 동일 출처 정책에 대한 예외를 가능하게 하는 메커니즘이다.

"CSP":

Content Security Policy [W3C.CSP3]는 특정 웹 페이지가 어떤 리소스를 가져오거나 실행할 수 있는지를 제한하는 메커니즘이다.

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

OAuth 2.0이 [RFC6749][RFC6750]에서 처음 명세화되었을 당시, 브라우저 기반 JavaScript 애플리케이션은 동일 출처 정책을 엄격하게 준수하는 해결책이 필요했다. OAuth 2.0의 일반적인 배포에서는 애플리케이션이 권한 부여 서버와 다른 도메인에서 실행되었으므로, cross-origin POST 요청이 필요한 Authorization Code 권한 부여 유형([RFC6749]의 4.1절)을 사용하는 것이 역사적으로 가능하지 않았다. 이 제한은 URL의 fragment 부분을 통해 프런트 채널에서 접근 토큰을 반환하여 cross-origin POST 요청의 필요성을 우회하는 Implicit 흐름([RFC6749]의 4.2절) 정의의 동기 중 하나였다.

그러나 Implicit 흐름에는 일반적으로 URL에 접근 토큰이 노출되는 것과 관련된 취약점을 포함하는 여러 단점이 있다. 이러한 공격에 대한 분석과 브라우저에서 Implicit 흐름을 사용할 때의 단점은 7.2절을 참조한다. 추가 공격과 보안 고려 사항은 [RFC9700]에서 찾을 수 있다.

현대 웹 개발에서는 Cross-Origin Resource Sharing (CORS) [Fetch] (동일 출처 정책에 대한 예외를 가능하게 함)의 광범위한 채택으로 인해, 브라우저 기반 애플리케이션은 OAuth 2.0 Authorization Code 흐름을 사용하고 토큰 엔드포인트에서 권한 부여 코드를 접근 토큰으로 교환하기 위해 POST 요청을 할 수 있게 되었다. Authorization Code 권한 부여 유형은 갱신 토큰 사용을 가능하게 하므로, 이러한 동작은 브라우저 기반 클라이언트에도 채택되었다. 다만 이러한 클라이언트는 여전히 안전한 저장소에 대한 접근이 제한적이거나 거의 없는 공개 클라이언트([RFC6749]의 2.1절에 정의됨)이다. 또한 흐름에 Proof Key for Code Exchange (PKCE) [RFC7636]를 추가하면 권한 부여 코드 주입을 방지하고, 권한 부여 코드가 가로채이더라도 공격자가 사용할 수 없도록 보장한다.

이러한 이유와 다른 교훈을 바탕으로, 브라우저 기반 애플리케이션의 현재 모범 사례는 PKCE와 함께 OAuth 2.0 Authorization Code 권한 부여 유형을 사용하는 것이다. 브라우저 기반 애플리케이션을 배포하기 위한 다양한 아키텍처 패턴이 있으며, 여기에는 대응하는 서버 측 구성 요소가 있는 경우와 없는 경우가 모두 포함된다. 이러한 각 아키텍처에는 이 문서에서 더 자세히 논의되는 고유한 절충점과 고려 사항이 있다. 추가 고려 사항은 제1자 공통 도메인 애플리케이션에 적용된다.

5. 악성 JavaScript의 위협

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

무엇보다도 먼저, 공격자가 애초에 발판을 얻지 못하도록 사전 예방 조치를 취하는 것이 중요하다. 여기에는 다음이 포함되지만 이에 한정되지는 않는다:

추가 권고 사항은 OWASP Cheat Sheet 시리즈 [OWASPCheatSheet]에서 찾을 수 있다.

안타깝게도, 역사는 이러한 보안 지침을 적용하더라도 공격자가 악성 JavaScript 실행을 유발하는 방법을 찾을 위험이 여전히 남아 있음을 보여준다. 악성 JS가 존재하는 상황에서 브라우저 기반 애플리케이션의 보안을 분석할 때, 악성 JavaScript 코드는 합법적인 애플리케이션 코드와 동일한 권한을 가진다는 점을 인식하는 것이 중요하다. 모든 JS 애플리케이션은 어느 정도 이 위험에 노출되어 있다.

애플리케이션은 그 기능 수행에 필요한 권한 부여를 부여하는 OAuth 토큰을 얻을 수 있다. 결합하면, 이는 손상된 코드가 그 권한 부여를 악의적인 목적에 사용할 수 있는 능력을 효과적으로 부여한다. 공격자가 권한 부여를 악용할 위험은 피할 수 없지만, 손상된 애플리케이션이 해당 권한 부여를 악용할 수 있는 정도를 제한하는 방법은 있다. 예를 들어, 이러한 접근은 애플리케이션이 활성 사용 중인 시간으로 제한하거나, 얻을 수 있는 토큰의 유형을 제한하거나, 토큰을 브라우저에 바인딩함으로써 제한될 수 있다.

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

브라우저 기반 애플리케이션에 대한 악성 JavaScript의 영향은 널리 연구되고 잘 이해된 주제이다. 그러나 OAuth 클라이언트로 동작하는 브라우저 기반 애플리케이션에 대한 악성 JavaScript의 구체적인 영향은 매우 독특한데, 악성 JavaScript가 이제 OAuth 흐름 중 상호 작용에 영향을 미칠 수 있기 때문이다. 이 절은 OAuth 클라이언트의 책임을 지닌 브라우저 기반 애플리케이션에 악성 JS 코드가 제기하는 위협을 살펴본다. 5.1절은 공격자가 악성 JavaScript 코드를 실행할 방법을 찾은 뒤 사용할 수 있는 몇 가지 시나리오를 논의한다. 이러한 시나리오는 공격자의 실제 능력이 단순한 토큰 유출을 훨씬 넘어선다는 명확한 그림을 제시한다. 5.2절은 이러한 공격 시나리오가 OAuth 클라이언트에 미치는 영향을 분석한다.

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

5.1. 공격 시나리오

이 절은 공격자가 악성 JavaScript 코드를 실행할 수 있게 하는 취약점을 발견한 뒤 실행할 수 있는 여러 공격 시나리오를 제시한다. 공격 시나리오에는 단순한 시나리오(5.1.1절)와 정교한 시나리오(5.1.3절)가 포함된다. 이 열거는 포괄적이지 않으며, OAuth 관련 기능에 좁게 한정되어 있고, 특정 순서 없이 제시된다는 점에 유의한다.

5.1.1. 단일 실행 토큰 탈취

이 시나리오는 공격자가 클라이언트의 현재 토큰을 획득하고 유출하는 단순한 토큰 유출 공격을 다룬다. 이 시나리오는 다음 단계로 구성된다:

  • 악성 JS 코드 실행

  • 애플리케이션이 선호하는 저장 메커니즘에서 토큰 획득(8절 참조)

  • 공격자가 제어하는 서버로 토큰 전송

  • 탈취한 토큰 저장 또는 악용

손상된 접근 토큰과 관련된 위험을 줄이기 위한 권장 방어 전략은 토큰의 범위와 수명을 줄이는 것이다. 갱신 토큰의 경우, 갱신 토큰 회전([RFC9700]의 4.14.2절에 정의됨)을 사용하면 탐지 및 수정 메커니즘을 제공한다. 송신자 제한 토큰(9.2절)은 탈취된 접근 토큰에 대한 추가 보호 계층을 제공한다.

이 공격 시나리오는 단순하며 악성 JavaScript의 위험을 설명하는 데 자주 사용된다는 점에 유의한다. 브라우저 기반 애플리케이션의 보안을 논의할 때, 공격자의 능력을 이 시나리오에서 논의한 공격으로 제한하지 않는 것이 중요하다.

5.1.2. 지속적 토큰 탈취

이 공격 시나리오는 단일 실행 토큰 탈취 시나리오(5.1.1절)의 더 고급 변형이다. 악성 코드가 실행되자마자 즉시 토큰을 훔치는 대신, 공격자는 애플리케이션의 토큰을 지속적으로 훔치기 위한 필요한 핸들러를 설정한다. 이 시나리오는 다음 단계로 구성된다:

  • 악성 JS 코드 실행

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

    • 애플리케이션이 선호하는 저장 메커니즘에서 토큰 획득(8절 참조)

    • 공격자가 제어하는 서버로 토큰 전송

    • 토큰 저장

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

이 시나리오의 핵심적인 차이는 공격자가 애플리케이션이 사용하는 최신 토큰에 항상 접근할 수 있다는 점이다. 이 공격 시나리오의 작은 변형만으로도 짧은 수명이나 갱신 토큰 회전과 같은 일반적인 토큰 탈취 방어책을 무력화하기에 충분하다.

접근 토큰의 경우, 공격자는 사용자의 브라우저가 온라인 상태인 동안 최신 접근 토큰을 계속 획득한다. 갱신 토큰 회전은 갱신 토큰의 악용을 방지하기에 충분하지 않다. 공격자는 애플리케이션이 최신 갱신 토큰을 사용하지 못하도록 쉽게 보장할 수 있다. 예를 들어, 공격자는 토큰을 훔친 뒤 애플리케이션의 토큰을 지우거나, 사용자가 애플리케이션을 닫을 때까지 기다리거나, 사용자의 브라우저가 오프라인이 될 때까지 기다릴 수 있다. 애플리케이션이 최신 갱신 토큰을 사용하지 않기 때문에, 탐지 가능한 갱신 토큰 재사용이 없으며, 공격자는 탈취한 갱신 토큰을 완전히 제어할 수 있다.

5.1.3. 새 토큰의 획득 및 추출

이 고급 공격 시나리오에서 공격자는 애플리케이션이 이미 획득한 모든 토큰을 완전히 무시한다. 대신 공격자는 애플리케이션의 출처와 연결된 악성 코드를 실행할 수 있는 능력을 이용한다. 그 능력으로 공격자는 숨겨진 iframe을 삽입하고 조용한 Authorization Code 흐름을 시작할 수 있다. 이 조용한 흐름은 권한 부여 서버와 사용자의 기존 세션을 재사용하며, 새롭고 독립적인 접근 토큰 (그리고 선택적으로 갱신 토큰)의 발급으로 이어진다. 이 시나리오는 다음 단계로 구성된다:

  • 악성 JS 코드 실행

  • iframe에서 권한 부여 코드를 얻기 위한 핸들러 설정 (예: 프레임의 URL을 모니터링하거나 Web Messaging [WebMessaging]을 통해)

  • 숨겨진 iframe을 페이지에 삽입하고 권한 부여 요청으로 초기화한다. iframe의 권한 부여 요청은 사용자의 세션 내에서 발생하며, 세션이 아직 활성 상태이면 권한 부여 코드의 발급으로 이어진다. 이 단계는 이 시나리오의 마지막 단락에서 논의한 것처럼 Authorization Server가 조용한 프레임 기반 흐름을 지원하는 데 의존한다는 점에 유의한다.

  • 이전에 설치한 핸들러를 사용하여 iframe에서 권한 부여 코드를 추출한다

  • 권한 부여 코드를 공격자가 제어하는 서버로 전송한다

  • 권한 부여 코드를 새 토큰 집합으로 교환한다

  • 탈취한 토큰을 악용한다

이 시나리오에서 가장 중요한 핵심은 기존 토큰을 훔치는 데 초점을 맞추는 대신 새 OAuth 흐름을 실행한다는 점이다. 본질적으로, 애플리케이션이 저장된 토큰을 공격자로부터 완전히 격리할 수 있는 토큰 저장 메커니즘을 찾더라도, 공격자는 여전히 새 토큰 집합을 요청할 수 있다. 공격자가 브라우저의 애플리케이션을 제어하므로, 공격자의 Authorization Code 흐름은 합법적인 Authorization Code 흐름과 구별할 수 없다는 점에 유의한다.

이 공격 시나리오는 공개 브라우저 기반 OAuth 클라이언트의 보안이 전적으로 redirect URI와 애플리케이션의 출처에 의존하기 때문에 가능하다. 공격자가 애플리케이션의 출처에서 악성 JavaScript 코드를 실행하면, 동일 출처 프레임을 검사할 수 있는 능력을 얻는다. 그 결과, 주 실행 컨텍스트에서 실행되는 공격자의 코드는 같은 출처 프레임에 로드된 redirect URI를 검사하여 권한 부여 코드를 추출할 수 있다.

프런트엔드 애플리케이션에는 이 공격 시나리오에 대응하는 실용적인 보안 메커니즘이 없다. 짧은 접근 토큰 수명과 갱신 토큰 회전은 공격자가 새롭고 독립적인 토큰 집합을 가지고 있으므로 효과가 없다. DPoP [RFC9449]와 같은 고급 보안 메커니즘도 동일하게 효과가 없는데, 공격자가 새로 획득한 토큰에 대해 자신의 키 쌍을 사용해 DPoP를 설정하고 사용할 수 있기 때문이다. 모든 Authorization Code 흐름마다 사용자 상호 작용을 요구하면 새 토큰의 자동 조용한 발급을 사실상 중단시킬 수 있지만, 이는 첫 페이지 로드 시 애플리케이션을 부트스트랩하거나 여러 관련 애플리케이션 간 단일 로그온을 사용하는 등 널리 정착된 패턴에 큰 영향을 미치므로 실용적인 조치가 아니다.

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

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

  • 악성 JS 코드 실행

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

리소스 서버에 대한 요청을 권한 부여하기 위해, 공격자는 단순히 클라이언트 애플리케이션의 동작을 모방한다. 예를 들어 클라이언트 애플리케이션이 발신 요청에 접근 토큰을 프로그래밍 방식으로 첨부하면, 공격자도 동일하게 수행한다. 클라이언트 애플리케이션이 적절한 접근 토큰으로 요청을 보강하기 위해 외부 구성 요소에 의존하는 경우, 이 외부 구성 요소도 공격자의 요청을 보강한다.

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

5.2. 공격 결과

공격 시나리오가 성공적으로 실행되면 접근 토큰과 갱신 토큰의 탈취, 또는 사용자의 브라우저에서 실행 중인 클라이언트 애플리케이션을 하이재킹할 수 있는 능력으로 이어질 수 있다. 이러한 각 결과는 브라우저 기반 OAuth 클라이언트와 관련이 있다. 아래에서는 심각도가 높은 순서에서 낮은 순서로 논의한다.

5.2.1. 탈취된 갱신 토큰 악용

공격자가 브라우저 기반 OAuth 클라이언트에서 유효한 갱신 토큰을 획득하면, 권한 부여 서버와 함께 Refresh Token 권한 부여를 실행하여 갱신 토큰을 악용할 수 있다. Refresh Token 권한 부여의 응답에는 접근 토큰이 포함되며, 이는 공격자에게 보호된 리소스에 접근할 수 있는 능력을 제공한다(5.2.2절 참조). 본질적으로, 탈취된 갱신 토큰을 악용하면 리소스 서버에 대해 합법적인 클라이언트 애플리케이션을 장기적으로 가장할 수 있다.

공격은 권한 부여 서버가 만료되었거나 회전된 갱신 토큰을 거부하거나, 갱신 토큰이 폐기될 때에만 중단된다. 일반적인 브라우저 기반 OAuth 클라이언트에서는 갱신 토큰이 여러 시간 또는 심지어 며칠 동안 유효하게 남아 있는 일이 드물지 않다.

5.2.2. 탈취된 접근 토큰 악용

공격자가 유효한 접근 토큰을 획득하면, 리소스 서버에 대한 요청에서 합법적인 클라이언트 애플리케이션을 가장할 수 있는 능력을 얻는다. 구체적으로, 접근 토큰을 소유하면 공격자는 유효한 접근 토큰을 수락하는 모든 리소스 서버에 임의의 요청을 보낼 수 있다. 본질적으로, 탈취된 접근 토큰을 악용하면 리소스 서버에 대해 합법적인 클라이언트 애플리케이션을 단기적으로 가장할 수 있다.

공격은 접근 토큰이 만료되거나 권한 부여 서버에서 토큰이 폐기될 때 끝난다. 일반적인 브라우저 기반 OAuth 클라이언트에서 접근 토큰의 수명은 분에서 시간에 이르기까지 상당히 짧을 수 있다.

접근 토큰을 소유하면 공격자가 이를 제한 없이 사용할 수 있다는 점에 유의한다. 공격자는 어떤 HTTP 메서드, 대상 URL, 헤더 값 또는 본문을 사용해서든 리소스 서버에 임의의 요청을 보낼 수 있다.

애플리케이션은 DPoP를 사용하여 접근 토큰이 브라우저가 보유한 내보낼 수 없는 키에 바인딩되도록 보장할 수 있다. 이 경우 공격자가 탈취한 접근 토큰을 악용하기가 훨씬 더 어려워진다. 더 구체적으로 DPoP를 사용하면, 공격자는 사용자의 브라우저에서 증명이 계산되는 온라인 공격을 수행하는 방식으로만 탈취한 애플리케이션 토큰을 악용할 수 있다. 이 공격은 [RFC9449]의 11.4절에 자세히 설명되어 있다. 그러나 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 클라이언트([RFC6749]의 2.1절에 정의됨)로서 권한 부여 서버와 상호 작용한다

  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. 애플리케이션 아키텍처

Authorization Token Resource Endpoint Endpoint Server (F) (K) Backend for Frontend (BFF) (D) (B,I) (C) (E) (G) (J) (L) (A,H) Static Web Host Browser
그림 1: OAuth 2.0 BFF 패턴

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

활성 세션이 발견되지 않으면, 브라우저 기반 애플리케이션은 PKCE 확장(6.1.3.1절에 설명됨)을 사용하는 Authorization Code 흐름을 시작하기 위해 BFF로의 내비게이션을 트리거한다(C). 이에 대해 BFF는 브라우저를 권한 부여 엔드포인트로 리디렉션하여 응답한다(D). 사용자가 다시 리디렉션되면, 브라우저는 권한 부여 코드를 BFF에 전달하고(E), BFF는 자신의 클라이언트 자격 증명과 PKCE 코드 검증자를 사용하여 토큰 엔드포인트에서 이를 토큰으로 교환할 수 있다(F).

BFF는 획득한 토큰을 사용자의 세션과 연결하고(6.1.2.3절 참조), 이 세션을 추적하기 위해 응답에 쿠키를 설정한다(G). 이 시점에서 리디렉션 기반 Authorization Code 흐름이 완료되었으므로, 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" 엔드포인트(위 그림의 B 및 I 단계)는 브라우저 기반 애플리케이션이 호출하는 API 엔드포인트이다. 요청에는 사용 가능한 경우 세션 정보가 포함되어, BFF가 활성 세션이 있는지 확인할 수 있게 한다. 응답은 세션이 활성 상태인지 여부를 브라우저 기반 애플리케이션에 표시해야 한다. 또한 BFF는 인증된 사용자에 대한 신원 정보와 같은 다른 정보를 포함할 수 있다.

Authorization Code 흐름을 시작하는 엔드포인트 (C 단계)는 브라우저가 내비게이션을 통해 접촉한다. 애플리케이션이 세션을 확인한 뒤(B 단계) 인증되지 않은 상태를 감지하면, 브라우저를 이 엔드포인트로 이동시킬 수 있다. 이렇게 하면 BFF가 리디렉션으로 응답하여 브라우저를 권한 부여 서버로 이동시킬 수 있다. 이 흐름을 시작하는 엔드포인트는 일반적으로 웹 서버에서 실행되는 기밀 클라이언트용 OAuth 2.0을 지원하는 라이브러리에서 "login" 엔드포인트로 포함된다. BFF가 활성 세션이 없음을 감지했을 때 B 단계에서 Authorization Code 흐름을 시작할 수도 있다는 점에 유의한다. 이 경우 BFF는 응답에 권한 부여 URI를 반환하고, 애플리케이션이 이 URI로 내비게이션 이벤트를 트리거하기를 기대한다. 그러나 이 시나리오는 사용자 지정 구현이 필요하며 표준 OAuth 라이브러리를 사용하기 더 어렵게 만든다.

권한 부여 코드를 수신하는 엔드포인트(E 단계)는 브라우저 내부의 내비게이션 이벤트에 의해 호출된다. 이 시점에서 애플리케이션은 로드되어 있지 않으며 리디렉션을 처리할 위치에 있지 않다. 흐름 시작과 유사하게, 리디렉션을 처리하는 엔드포인트는 표준 OAuth 라이브러리에서 제공된다. BFF는 애플리케이션을 로드하도록 브라우저를 트리거하는 리디렉션으로 이 요청에 응답할 수 있다.

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

6.1.2.2. 갱신 토큰

[RFC9700]의 4.14절에 설명된 것처럼 갱신 토큰을 사용할 때, BFF는 갱신 토큰(F 단계)을 획득하고 이를 사용자의 세션과 연결한다.

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

갱신 토큰이 만료되면, 완전히 새로운 Authorization Code 흐름을 실행하지 않고는 유효한 접근 토큰을 얻을 방법이 없다. 따라서 BFF가 관리하는 쿠키 기반 세션의 수명을 갱신 토큰의 최대 수명과 같게 구성하는 것이 타당하다. 또한 BFF가 활성 세션의 갱신 토큰이 더 이상 유효하지 않음을 알게 되면, 세션도 무효화하는 것이 타당하다.

BFF는 사용자의 토큰에 접근하는 데 사용되는 사용자의 세션을 추적하기 위해 브라우저 쿠키([I-D.ietf-httpbis-rfc6265bis])에 의존한다. 서버 측 및 클라이언트 측 쿠키 기반 세션에는 몇 가지 단점이 있다.

서버 측 세션은 세션 식별자만 노출하고 모든 데이터를 서버에 보관한다. 이렇게 하면 활성 세션에 대한 높은 수준의 제어와 원하는 시점에 어떤 세션이든 폐기할 수 있는 가능성이 보장된다. 이 접근 방식의 단점은 확장성에 미치는 영향이며, "sticky sessions" 또는 "session replication"과 같은 해결책이 필요하다. 이러한 단점을 고려할 때, 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 Token을 받는다. BFF는 ID Token의 정보를 사용자의 세션과 연결하고 이를 B 단계 또는 I 단계에서 애플리케이션에 제공할 수 있다.

필요할 때 BFF는 사용자의 세션과 연결된 접근 토큰을 사용하여 UserInfo 엔드포인트로 요청을 보낼 수 있다.

6.1.2.5. 실용적 배포 전략

정적 JavaScript 코드를 제공하는 것은 OAuth 토큰 처리 및 요청 전달과 별도의 책임이다. 위에 제시된 그림에서 BFF와 정적 웹 호스트는 두 개의 별도 엔티티로 표시된다. 실제 배포에서는 이러한 구성 요소가 단일 서비스(즉, BFF가 정적 JS 코드를 제공), 두 개의 별도 서비스(즉, CDN과 BFF), 또는 단일 서비스 내 두 구성 요소 (즉, 클라우드 플랫폼의 정적 호스팅과 서버리스 함수)로 배포될 수 있다.

특정 시나리오에 맞추기 위해 이 아키텍처를 더 사용자 지정할 수 있다는 점에 유의한다. 예를 들어, 내부 및 외부 리소스 서버 모두에 의존하는 애플리케이션은 내부 리소스 서버를 BFF와 함께 호스팅하도록 선택할 수 있다. 이 시나리오에서 내부 리소스 서버로의 요청은 네트워크를 통해 요청을 전달할 필요 없이 BFF에서 직접 처리된다. 리소스 서버의 관점에서 권한 부여는 변하지 않으며, 사용자의 세션은 내부적으로 접근 토큰과 그 클레임으로 변환된다.

6.1.3. 보안 고려 사항

6.1.3.1. Authorization Code 권한 부여

BFF를 사용하는 주요 이점은 BFF가 기밀 클라이언트로 동작할 수 있는 능력이다. 따라서 BFF는 권한 부여 서버와 자격 증명을 설정하여 기밀 클라이언트로 동작해야 한다(MUST). 또한 BFF는 접근 토큰 요청을 시작하기 위해 [RFC9700]의 2.1.1절에 설명된 OAuth 2.0 Authorization Code 권한 부여를 사용해야 한다(MUST).

6.1.3.3. 교차 사이트 요청 위조 보호

브라우저 기반 애플리케이션과 BFF 사이의 상호 작용은 인증 및 권한 부여를 위해 쿠키에 의존한다. 다른 쿠키 기반 상호 작용과 유사하게, BFF는 Cross-Site Request Forgery (CSRF) 공격을 고려해야 한다. 성공적인 CSRF 공격은 공격자의 BFF 요청이 보호된 리소스로 나가는 호출을 트리거하도록 허용할 수 있다.

BFF는 적절한 CSRF 방어를 구현해야 한다(MUST). 정확한 메커니즘 또는 메커니즘의 조합은 아래에서 논의한 것처럼 BFF가 배포된 정확한 도메인에 따라 달라진다.

6.1.3.3.2. Cross-Origin Resource Sharing

BFF는 CORS를 CSRF 방어 메커니즘으로 의존할 수 있다. CORS는 서버가 적절한 CORS 헤더를 설정하여 그러한 요청을 명시적으로 승인하지 않는 한, cross-origin 요청을 제한하는 브라우저 구현 보안 메커니즘이다.

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

이 속성 때문에 BFF는 CORS를 CSRF 방어로 의존할 수 있다. 공격자가 사용자의 브라우저에서 BFF로 cross-origin 요청을 시작하려 할 때, BFF는 preflight 응답에서 요청을 승인하지 않아 브라우저가 실제 요청을 차단하게 한다. 공격자는 항상 자신의 컴퓨터에서 요청을 시작할 수 있지만, 그러면 요청에 사용자의 쿠키가 포함되지 않으므로 공격은 실패한다는 점에 유의한다.

CORS를 CSRF 방어로 의존할 때는 특정 요청이 preflight 없이 가능하다는 점을 인식하는 것이 중요하다. "CORS-safelisted Requests"라고 불리는 이러한 요청의 경우, 브라우저는 단순히 요청을 보내고, 서버가 적절한 CORS 헤더를 보내지 않았다면 응답에 대한 접근을 방지한다. 이 동작은 GET 요청이나 form 기반 POST 요청처럼 JavaScript 이외의 수단으로 트리거될 수 있는 요청에 적용된다.

이 동작의 결과로, 리소스 서버의 특정 엔드포인트는 CORS가 방어로 활성화되어 있어도 CSRF에 취약해질 수 있다. 예를 들어 리소스 서버가 body 없는 POST 요청을 받는 엔드포인트를 노출하는 API라면, preflight 요청이 없고 CSRF 방어도 없다.

CORS 정책에 대한 이러한 우회를 피하기 위해, BFF는 브라우저 기반 애플리케이션이 사용자 지정 요청 헤더를 포함하도록 요구하는 것이 좋다(SHOULD). 사용자 지정 요청 헤더가 있는 cross-origin 요청은 항상 preflight를 필요로 하므로, CORS가 효과적인 CSRF 방어가 된다. 이 메커니즘을 사용할 때 BFF는 모든 들어오는 요청이 이 정적 헤더를 포함하는지 확인해야 한다(MUST). 이 헤더의 정확한 이름은 애플리케이션과 BFF의 재량에 따른다. 예시 구성은 My-Static-Header: 1과 같은 정적 값을 가진 요청 헤더일 수 있다.

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

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

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

이 메커니즘이 CORS 접근 방식보다 반드시 권장되는 것은 아니라는 점에 유의한다. 그러나 프레임워크가 이 메커니즘에 대한 내장 지원을 제공한다면, CSRF 방어를 위한 낮은 노력의 대안으로 사용될 수 있다.

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

BFF 패턴은 브라우저 기반 애플리케이션이 리소스 서버로 보내는 모든 요청을 백엔드 BFF 구성 요소를 통해 전달하도록 요구한다. 그 결과 BFF 구성 요소는 애플리케이션과 리소스 서버 사이의 모든 요청과 응답을 관찰할 수 있으며, 이는 상당한 개인정보 보호 영향을 가질 수 있다.

브라우저 기반 애플리케이션과 BFF가 같은 당사자에 의해 구축되고 배포되는 경우, 개인정보 보호 영향은 대체로 미미할 가능성이 높다. 그러나 이 패턴이 제3자가 제공하거나 호스팅하는 BFF 구성 요소를 사용하여 구현되는 경우, 이러한 개인정보 보호 영향을 고려해야 한다.

6.1.3.5. 운영 고려 사항

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

6.1.3.6. 프록시 제한

BFF는 프런트엔드의 요청을 수락하고 리소스 서버로 전달함으로써 프록시 서비스로 동작한다. 들어오는 요청은 쿠키를 포함하며, BFF는 이를 나가는 요청의 접근 토큰으로 변환한다. (이는 HTTP 프록시라기보다 애플리케이션 계층 리버스 프록시에 더 가깝다는 점에 유의한다.) CSRF 공격 외에도, 공격자는 BFF를 조작하여 의도하지 않은 호스트로 요청을 전달하게 하려 시도할 수 있다. 공격자가 이를 성공적으로 악용하면, BFF를 임의의 서버로 리디렉션하여 사용자의 접근 토큰을 잠재적으로 노출할 수 있다.

이 위험을 완화하기 위해, BFF는 요청을 전달하기 전에 목적지 호스트를 검증하여 엄격한 발신 요청 제어를 강제해야 한다(MUST). 이를 위해 승인된 리소스 서버의 명시적 allowlist를 유지해야 하며, 요청이 사전 정의된 백엔드로만 프록시되도록 보장해야 한다(예: /bff/orders/createhttps://order-api.example.com/create에만 매핑됨). 경로 기반 동적 라우팅(예: /bff/orders/{id})이 필요한 경우, BFF는 승인된 목적지만 접근 가능하도록 엄격한 검증을 적용해야 한다(MUST). 또한 엔드포인트별로 허용되는 HTTP 메서드를 제한하면 공격 벡터를 추가로 줄일 수 있다.

동적으로 구성 가능한 프록시를 구현할 때, BFF는 명시적으로 허용된 호스트와 경로에 대한 요청만 허용하도록 보장해야 한다(MUST). 이러한 제한을 강제하지 못하면 무단 접근과 접근 토큰 유출로 이어질 수 있다.

6.1.3.7. 고급 보안

BFF 패턴에서는 모든 OAuth 책임이 기밀 클라이언트로 동작하는 서버 측 구성 요소인 BFF로 이동되었다. 서버 측 애플리케이션은 브라우저 기반 애플리케이션보다 더 통제된 환경에서 실행되므로, 고급 OAuth 보안 관행을 채택하기가 더 쉬워진다. 예에는 키 기반 클라이언트 인증 및 송신자 제한 토큰이 포함된다.

6.1.4. 위협 분석

이 절은 5절의 공격 시나리오와 결과를 다시 살펴보고, 잠재적인 추가 방어책을 논의한다.

6.1.4.1. 공격 시나리오 및 결과

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

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

이 공격 시나리오는 다음 결과로 이어진다는 점에 유의한다:

클라이언트 하이재킹은 브라우저 기반 애플리케이션의 본질에 내재한 공격 시나리오라는 점에 유의한다. 따라서 애초에 악성 코드 실행을 막는 것 외에는 이러한 공격을 방지할 수 있는 것이 없다. 이를 달성하는 데 도움이 되는 기법으로는 보안 코딩 지침 준수, 코드 분석, Content Security Policy([W3C.CSP3])와 같은 심층 방어 메커니즘 배포가 있다.

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

마지막으로, BFF는 브라우저 기반 애플리케이션과 리소스 서버 사이의 모든 트래픽을 관찰할 수 있는 독특한 위치에 있다. 고보안 애플리케이션이 이상 탐지나 속도 제한을 구현하려 한다면, 이러한 BFF가 이를 수행하기에 이상적인 위치가 될 것이다. 이러한 제한은 클라이언트 하이재킹의 결과를 완화하는 데 추가로 도움이 될 수 있다.

6.1.4.2. 완화된 공격 시나리오

아래에 나열된 다른 공격 시나리오들은 BFF 애플리케이션 아키텍처에 의해 효과적으로 완화된다:

BFF는 브라우저 기반 애플리케이션에 어떤 토큰도 노출하지 않음으로써 앞의 두 공격 시나리오에 대응한다. 공격자가 애플리케이션을 완전히 제어하더라도, 훔칠 토큰이 전혀 없다.

공격자가 조용한 흐름을 실행하여 새 접근 토큰(그리고 선택적으로 갱신 토큰)을 얻는 세 번째 시나리오는 BFF를 기밀 클라이언트로 만들면 완화된다. 공격자가 권한 부여 코드를 얻는 데 성공하더라도, 클라이언트 자격 증명이 없기 때문에 이 코드를 교환할 수 없다. 또한 PKCE를 사용하면 권한 부여 코드에 대한 다른 공격을 방지한다.

갱신 토큰과 접근 토큰은 BFF가 관리하며 브라우저에 노출되지 않으므로, 잠재적 공격의 다음 두 결과는 관련이 없어지게 된다:

  • 탈취된 갱신 토큰 악용(5.2.1절 참조)

  • 탈취된 접근 토큰 악용(5.2.2절 참조)

6.1.4.3. 요약

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

그러나 BFF 아키텍처 패턴의 특성상, 강력한 보안 보장을 제공한다. BFF를 사용하면 OAuth를 사용함으로써 애플리케이션의 공격 표면이 증가하지 않도록 보장할 수도 있다. 유일하게 가능한 공격 패턴은 사용자의 브라우저에서 클라이언트 애플리케이션을 하이재킹하는 것이며, 이는 웹 애플리케이션에 내재한 문제이다.

이 아키텍처는 비즈니스 애플리케이션, 민감한 애플리케이션 및 개인 데이터를 처리하는 애플리케이션에 강력히 권장된다.

6.2. 토큰 중개 백엔드

이 절은 기밀 클라이언트([RFC6749]의 2.1절에 정의됨)로서 토큰을 얻기 위한 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. 애플리케이션 아키텍처

Authorization Token Resource Endpoint Endpoint Server (F) Token-Mediating Backend (J) (D) (B,I) (C) (E) (G) (A,H) Static Web Host Browser
그림 2: OAuth 2.0 토큰 중개 백엔드 패턴

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

활성 세션이 발견되지 않으면, 애플리케이션은 PKCE 확장(6.2.3.1절에 설명됨)을 사용하는 Authorization Code 흐름을 시작하기 위해 토큰 중개 백엔드로의 내비게이션을 트리거한다(C). 이에 대해 토큰 중개 백엔드는 브라우저를 권한 부여 엔드포인트로 리디렉션하여 응답한다(D). 사용자가 다시 리디렉션되면, 브라우저는 권한 부여 코드를 토큰 중개 백엔드에 전달하고(E), 토큰 중개 백엔드는 자신의 클라이언트 자격 증명과 PKCE 코드 검증자를 사용하여 토큰 엔드포인트에서 이를 토큰으로 교환할 수 있다(F).

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

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

6.2.2. 구현 세부 사항

6.2.2.1. 세션 및 OAuth 엔드포인트

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

  • "check session" 엔드포인트(위 그림의 B 및 I 단계)는 브라우저 기반 애플리케이션이 호출하는 API 엔드포인트이다. 요청에는 사용 가능한 경우 세션 정보가 포함되어, 백엔드가 활성 세션이 있는지 확인할 수 있게 한다. 응답은 세션이 활성 상태인지 여부를 브라우저 기반 애플리케이션에 표시해야 한다. 활성 세션이 발견되면, 백엔드는 응답에 접근 토큰을 포함한다. 또한 백엔드는 인증된 사용자에 대한 신원 정보와 같은 다른 정보를 포함할 수 있다.

  • Authorization Code 흐름을 시작하는 엔드포인트 (C 단계)는 BFF 아키텍처에 대해 설명한 엔드포인트와 동일하다. 자세한 내용은 6.1.2.1절을 참조한다.

  • 권한 부여 코드를 수신하는 엔드포인트 (E 단계)는 BFF 아키텍처에 대해 설명한 엔드포인트와 동일하다. 자세한 내용은 6.1.2.1절을 참조한다.

  • logout을 지원하는 엔드포인트는 BFF 아키텍처에 대해 설명한 엔드포인트와 동일하다. 자세한 내용은 6.1.2.1절을 참조한다.

6.2.2.2. 갱신 토큰

[RFC9700]의 4.14절에 설명된 것처럼 갱신 토큰을 사용할 때, 토큰 중개 백엔드는 F 단계에서 갱신 토큰을 획득하고 이를 사용자의 세션과 연결한다.

리소스 서버가 접근 토큰을 거부하면, 애플리케이션은 토큰 중개 백엔드에 연락하여 새 접근 토큰을 요청할 수 있다. 토큰 중개 백엔드는 이 요청과 연결된 쿠키에 의존하여 사용자의 갱신 토큰을 조회하고, 갱신 토큰을 사용하여 토큰 요청을 한다. 이러한 단계는 그림에 표시되어 있지 않다. 이 Refresh Token 요청은 기밀 클라이언트인 백엔드에서 이루어지므로 클라이언트 인증이 필요하다는 점에 유의한다.

갱신 토큰이 만료되면, 완전히 새로운 Authorization Code 권한 부여를 시작하지 않고는 유효한 접근 토큰을 얻을 방법이 없다. 따라서 그러한 정보를 사전에 알고 있다면 쿠키 기반 세션의 수명을 갱신 토큰의 최대 수명과 같게 구성하는 것이 타당하다. 또한 토큰 중개 백엔드가 활성 세션의 갱신 토큰이 더 이상 유효하지 않음을 알게 되면, 세션을 무효화하는 것이 타당하다.

6.2.2.3. 접근 토큰 범위

접근하는 리소스 서버와 권한 부여 서버의 범위 구성에 따라, 애플리케이션은 서로 다른 범위 구성을 가진 접근 토큰을 요청하기를 원할 수 있다. 이 동작은 애플리케이션이 최소 범위의 접근 토큰을 사용하는 모범 사례를 따를 수 있게 한다.

애플리케이션은 활성 세션을 확인할 때(단계 A/I) 원하는 범위를 토큰 중개 백엔드에 알릴 수 있다. 이전에 획득한 접근 토큰이 원하는 범위 기준에 들어맞는지 결정하는 것은 토큰 중개 백엔드의 책임이다.

이 접근 토큰 캐싱 메커니즘이 토큰 중개 백엔드에서 무분별하게 적용되면 범위 상승 위험을 유발할 수 있다는 점에 유의해야 한다. 캐시된 접근 토큰이 프런트엔드가 요청한 범위의 상위 집합을 포함하는 경우, 토큰 중개 백엔드는 이를 프런트엔드에 반환하지 않는 것이 좋다(SHOULD NOT). 대신 갱신 토큰을 사용하여 권한 부여 서버에서 더 작은 범위 집합을 가진 접근 토큰을 요청하는 것이 좋다(SHOULD). 이러한 접근 토큰 downscoping 메커니즘의 지원은 권한 부여 서버의 재량에 따른다는 점에 유의한다.

토큰 중개 백엔드는 특정 리소스 서버에 대한 접근 토큰을 얻기 위해 [RFC8707]에 의존할 때, downscoping과 유사한 메커니즘을 사용할 수 있다.

BFF와 유사하게, 토큰 중개 백엔드는 사용자의 세션을 추적하기 위해 브라우저 쿠키에 의존한다. BFF와 동일한 구현 지침 및 보안 고려 사항이 적용되며, 이는 6.1.2.3절에서 논의된다.

6.2.2.5. OAuth와 OpenID Connect 결합

BFF와 유사하게, 토큰 중개 백엔드는 OAuth와 OpenID Connect를 단일 흐름으로 결합하도록 선택할 수 있다. 자세한 내용은 6.1.2.4절을 참조한다.

6.2.2.6. 실용적 배포 시나리오

정적 JavaScript 또는 WASM 코드를 제공하는 것은 권한 부여 서버와의 상호 작용을 처리하는 것과 별도의 책임이다. 위에 제시된 그림에서 토큰 중개 백엔드와 정적 웹 호스트는 두 개의 별도 엔티티로 표시된다. 실제 배포 시나리오에서 이러한 구성 요소는 단일 서비스(즉, 토큰 중개 백엔드가 정적 코드를 제공), 두 개의 별도 서비스(즉, CDN과 토큰 중개 백엔드), 또는 단일 서비스 내 두 구성 요소(즉, 클라우드 플랫폼의 정적 호스팅과 서버리스 함수)로 배포될 수 있다. 이러한 배포 차이는 이 패턴에서 설명한 관계에 영향을 주지 않지만, cross-origin 통신을 가능하게 하기 위해 CORS를 적절히 구성해야 하는 필요성과 같은 다른 실무적 사항에는 영향을 줄 수 있다.

6.2.3. 보안 고려 사항

6.2.3.1. Authorization Code 권한 부여

토큰 중개 백엔드를 사용하는 주요 이점은 백엔드가 기밀 클라이언트로 동작할 수 있는 능력이다. 따라서 토큰 중개 백엔드는 기밀 클라이언트로 동작해야 한다(MUST). 또한 토큰 중개 백엔드는 접근 토큰 요청을 시작하기 위해 [RFC9700]의 2.1.1절에 설명된 OAuth 2.0 Authorization Code 권한 부여를 사용해야 한다(MUST).

6.2.3.3. 교차 사이트 요청 위조 보호

브라우저 기반 애플리케이션과 토큰 중개 백엔드 사이의 상호 작용은 인증 및 권한 부여를 위해 쿠키에 의존한다. BFF와 마찬가지로, 토큰 중개 백엔드는 Cross-Site Request Forgery (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. 완화된 공격 시나리오

아래에 나열된 다른 공격 시나리오들은 토큰 중개 백엔드에 의해 효과적으로 완화된다:

  • 갱신 토큰에 대한 단일 실행 토큰 탈취(5.1.1절)

  • 갱신 토큰에 대한 지속적 토큰 탈취(5.1.2절)

  • 새 토큰의 획득 및 추출(5.1.3절)

토큰 중개 백엔드는 갱신 토큰을 브라우저 기반 애플리케이션에 노출하지 않음으로써 앞의 두 공격 시나리오에 대응한다. 공격자가 애플리케이션을 완전히 제어하더라도, 훔칠 갱신 토큰이 전혀 없다.

공격자가 조용한 흐름을 실행하여 새 접근 토큰(그리고 선택적으로 갱신 토큰)을 얻는 세 번째 시나리오는 토큰 중개 백엔드를 기밀 클라이언트로 만들면 완화된다. 공격자가 권한 부여 코드를 얻는 데 성공하더라도, 클라이언트 자격 증명이 없기 때문에 이 코드를 교환할 수 없다. 또한 PKCE를 사용하면 권한 부여 코드에 대한 다른 공격을 방지한다.

토큰 중개 백엔드의 특성 때문에, 잠재적 공격의 다음 결과는 관련이 없어지게 된다:

  • 탈취된 갱신 토큰 악용(5.2.1절 참조)

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. 애플리케이션 아키텍처

Authorization Resource Server Server (B) (C) (D) (E) (A) Static Web Host Browser
그림 3: 브라우저 기반 OAuth 2.0 클라이언트 패턴

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

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

그런 다음 애플리케이션은 8절에 설명된 적절한 브라우저 API를 사용하여 접근 토큰과 선택적 갱신 토큰을 가능한 한 안전하게 저장할 책임을 진다.

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

6.3.2. 구현 세부 사항

공개 클라이언트([RFC6749]의 2.1절)이고 [RFC6749]의 4.1절에 설명된 Authorization Code 권한 부여 유형을 사용하는 브라우저 기반 애플리케이션은 이 절에 설명된 다음 추가 요구 사항도 따라야 한다(MUST).

6.3.2.1. Authorization Code 권한 부여

공개 클라이언트인 브라우저 기반 애플리케이션은 접근 토큰을 얻을 때 Proof Key for Code Exchange (PKCE [RFC7636]) 확장을 구현해야 하며(MUST), 권한 부여 서버는 이러한 클라이언트를 위해 PKCE를 지원하고 강제해야 한다(MUST).

PKCE 확장은 권한 부여 코드가 가로채여 악성 클라이언트에 의해 접근 토큰으로 교환되는 공격을 방지한다. 이는 권한 부여 코드를 교환하는 클라이언트 인스턴스가 흐름을 시작한 동일한 인스턴스인지 권한 부여 서버가 확인할 수 있는 방법을 제공함으로써 이루어진다.

6.3.2.2. 교차 사이트 요청 위조 보호

브라우저 기반 애플리케이션은 redirect URI에 대한 CSRF 공격을 방지해야 한다(MUST). 이는 아래 중 하나로 달성할 수 있다:

  • 이 클라이언트에 대해 PKCE를 요구하도록 권한 부여 서버를 구성하기

  • CSRF 토큰을 전달하기 위해 OAuth state 매개변수에 고유 값을 사용하고 검증하기

  • 애플리케이션이 OpenID Connect도 사용하는 경우, [OpenID]에 설명된 대로 OpenID Connect nonce 매개변수를 사용하고 검증하기

Authorization Code 흐름을 위한 적절한 CSRF 방어를 선택하는 데 대한 추가 세부 사항은 [RFC9700]의 2.1절을 참조한다.

6.3.2.3. 갱신 토큰

브라우저 기반 클라이언트의 경우, 애플리케이션이 명시적으로 DPoP [RFC9449]를 사용하지 않는 한, 갱신 토큰은 일반적으로 bearer token이다. 그 결과, 유출된 갱신 토큰의 위험은 유출된 접근 토큰보다 더 크다. 공격자가 훔친 갱신 토큰을 사용하여 권한 부여 서버에 탐지되지 않을 가능성이 있는 상태로 새 접근 토큰을 계속 얻을 수 있기 때문이다.

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

특히 권한 부여 서버는 다음을 수행해야 한다:

  • 각 사용 시 갱신 토큰을 회전시키거나, [RFC9700]의 4.14.2절에 설명된 송신자 제한 갱신 토큰을 사용해야 한다(MUST)

  • 갱신 토큰에 최대 수명을 설정하거나, 갱신 토큰이 일정 시간 동안 사용되지 않은 경우 만료되도록 해야 한다(MUST)

  • 회전된 갱신 토큰을 발급할 때, 갱신 토큰에 사전 설정된 만료 시간이 있는 경우, 새 갱신 토큰의 수명을 초기 갱신 토큰의 수명을 넘어 연장해서는 안 된다(MUST NOT)

전체 갱신 토큰 수명을 초기 갱신 토큰의 수명으로 제한하면, 훔친 갱신 토큰이 무기한 사용될 수 없도록 보장한다.

예를 들면 다음과 같다:

  • 사용자가 애플리케이션을 승인하여, 10분 동안 지속되는 접근 토큰과 8시간 동안 지속되는 갱신 토큰이 발급된다

  • 10분 후 초기 접근 토큰이 만료되므로, 애플리케이션은 갱신 토큰을 사용하여 새 접근 토큰을 얻는다

  • 권한 부여 서버는 10분 동안 지속되는 새 접근 토큰과 7시간 50분 동안 지속되는 새 갱신 토큰을 반환한다

  • 이는 초기 권한 부여 시점부터 8시간이 지날 때까지 계속된다

  • 이 시점에서, 애플리케이션이 8시간 후에 갱신 토큰을 사용하려고 하면 요청이 실패하고, 애플리케이션은 사용자의 인증 또는 이전에 설정된 세션에 의존하는 Authorization Code 흐름을 다시 시작해야 한다

권한 부여 서버는 갱신 토큰의 수명을 권한 부여 서버와의 사용자의 인증된 세션에 연결하는 것이 좋다(SHOULD). 이렇게 하면 사용자가 로그아웃할 때, 브라우저 기반 애플리케이션에 이전에 발급된 갱신 토큰이 무효화되어 단일 로그아웃 시나리오를 모방한다. 권한 부여 서버는 다른 공개 클라이언트와 비교하여 브라우저 기반 애플리케이션에 대해 갱신 토큰 발급, 수명 및 만료에 관한 서로 다른 정책을 설정할 수 있다(MAY).

6.3.3. 보안 고려 사항

6.3.3.1. 클라이언트 인증

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

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

SPA 클라이언트에 정적으로 포함된 공유 비밀을 여전히 요구하는 권한 부여 서버는 클라이언트를 공개 클라이언트로 취급해야 하며(MUST), 그 비밀을 클라이언트 신원의 증명으로 받아들여서는 안 된다. 추가 조치가 없다면 이러한 클라이언트는 클라이언트 가장(아래 6.3.3.2절 참조)에 취약하다.

6.3.3.2. 클라이언트 가장

[RFC6749]의 10.2절에 명시된 것처럼, 권한 부여 서버는 클라이언트 애플리케이션의 신원을 보장할 수 있는 경우를 제외하고, 사용자 동의 또는 상호 작용 없이 권한 부여 요청을 자동으로 처리하지 않는 것이 좋다(SHOULD NOT).

권한 부여 서버가 redirect URI를 고정된 절대 HTTPS URI 집합으로 제한하여 와일드카드 도메인, 와일드카드 경로 또는 와일드카드 query string 구성 요소의 사용을 방지하는 경우, 등록된 절대 HTTPS URI의 정확한 일치는 client_id에 대한 이전 요청이 이미 승인된 경우 권한 부여 요청을 자동으로 처리할지 결정하기 위한 클라이언트의 신원 증명으로 권한 부여 서버에 의해 받아들여질 수 있다(MAY).

6.3.3.2.1. Authorization Code 리디렉션

클라이언트는 하나 이상의 redirect URI를 권한 부여 서버에 등록해야 하며(MUST), 권한 부여 요청에서는 정확히 등록된 redirect URI만 사용해야 한다.

권한 부여 서버는 [RFC9700]의 4.1.1절에 설명된 대로 등록된 redirect URI와 정확히 일치할 것을 요구해야 한다(MUST). 이는 권한 부여 코드를 대상으로 하는 공격을 방지하는 데 도움이 된다.

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

브라우저 기반 애플리케이션에서는 기본 창을 리디렉션하는 대신 팝업이나 iframe과 같은 보조 창에서 OAuth 흐름을 실행하는 것이 일반적이다. 이러한 흐름에서 브라우저 기반 앱은 예를 들어 페이지 새로고침을 피하거나 프레임 기반 흐름을 조용히 실행하기 위해 기본 창의 제어를 유지한다.

브라우저 기반 앱과 권한 부여 서버가 서로 다른 프레임에서 호출되는 경우, top-level 리디렉션 대신 postMessage API(a.k.a. [WebMessaging])와 같은 브라우저 내 통신 기법을 사용해야 한다. 메시지의 기밀성과 진정성을 보장하기 위해, postMessage의 송신자 origin과 수신자 origin은 postMessage API가 본질적으로 제공하는 메커니즘([WebMessaging]의 9.3.2절)을 사용하여 모두 검증해야 한다(MUST).

[RFC9700]의 4.18절은 브라우저 내 통신 흐름의 보안과 이러한 공격을 방어하기 위해 브라우저 기반 애플리케이션 및 권한 부여 서버가 적용해야 하는(MUST) 대응책에 대한 추가 세부 사항을 제공한다.

6.3.3.4. Cross-Origin 요청

이 시나리오에서 애플리케이션은 브라우저 API를 사용하여 권한 부여 서버와 리소스 서버로 요청을 보낸다. OAuth 2.0의 특성상 이러한 요청은 일반적으로 cross-origin이며, 브라우저가 강제하는 cross-origin 통신 제한의 대상이 된다. 권한 부여 서버와 리소스 서버는 애플리케이션이 필요한 cross-origin 요청을 할 수 있도록 필요한 CORS 헤더([Fetch]에 정의됨)를 보내야 한다(MUST). 브라우저 기반 OAuth 클라이언트가 권한 부여 서버 또는 리소스 서버와 동일한 출처에서 실행되는 이례적인 시나리오에서는, 필요한 상호 작용을 가능하게 하기 위해 CORS 정책이 필요하지 않다는 점에 유의한다.

권한 부여 서버의 경우, CORS 구성은 브라우저 기반 애플리케이션이 권한 부여 코드를 토큰으로 교환하는 토큰 엔드포인트와 관련이 있다. 또한 권한 부여 서버가 discovery metadata URL, JSON Web Key Sets, dynamic client registration, revocation, introspection 또는 user info 엔드포인트와 같은 추가 엔드포인트를 애플리케이션에 제공하는 경우, 이러한 엔드포인트도 브라우저 기반 애플리케이션에서 접근될 수 있다. 따라서 권한 부여 서버는 이러한 엔드포인트에서 CORS를 지원할 책임이 있다.

이 명세는 구체적인 CORS 정책 구현을 결정하기 위한 지침을 포함하지 않는다. 이는 와일드카드 origin 또는 더 제한적인 구성으로 이루어질 수 있다. CORS에는 서로 다른 보안 속성을 가진 두 가지 동작 모드가 있다는 점에 유의한다. 첫 번째 모드는 이전에 simple requests로 알려진 CORS-safelisted 요청에 적용되며, 브라우저는 요청을 보내고 CORS 응답 헤더를 사용하여 응답을 클라이언트 측 실행 컨텍스트에 노출할 수 있는지 결정한다. 사용자 지정 요청 헤더가 있는 요청과 같은 non-CORS-safelisted 요청의 경우, 브라우저는 먼저 preflight를 사용하여 CORS 정책을 확인한다. 브라우저는 서버가 preflight 응답에서 승인을 보낼 때에만 실제 요청을 보낸다.

권한 부여 서버의 특정 구성 때문에, preflight에 대한 CORS 응답이 실제 요청에 대한 CORS 응답과 다를 수 있다는 점에 유의한다. preflight 동안 권한 부여 서버는 제공된 origin만 검증할 수 있지만, 실제 요청 동안에는 권한 부여 서버가 클라이언트 ID와 같은 전체 요청 데이터를 가진다. 따라서 권한 부여 서버는 preflight 중에는 알려진 origin을 승인할 수 있지만, 실제 요청에서는 origin을 이 특정 클라이언트의 사전 등록된 origin 목록과 비교한 뒤 거부할 수 있다.

6.3.4. 위협 분석

이 절은 5절의 공격 시나리오와 결과를 다시 살펴보고, 잠재적인 추가 방어책을 논의한다.

6.3.4.1. 공격 시나리오 및 결과

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

가장 위험한 공격 시나리오는 새 토큰의 획득 및 추출이다. 이 공격 시나리오에서 공격자는 권한 부여 서버와만 상호 작용하므로, 클라이언트의 OAuth 기능에 대한 실제 구현 세부 사항은 관련이 없게 된다. 합법적인 클라이언트 애플리케이션이 토큰을 공격자로부터 완전히 격리할 방법을 찾더라도, 공격자는 여전히 권한 부여 서버에서 토큰을 얻을 수 있다.

이러한 공격 시나리오는 다음 결과로 이어진다는 점에 유의한다:

6.3.4.2. 추가 방어책

이 아키텍처는 본질적으로 악성 브라우저 기반 코드에 취약하지만, 애플리케이션의 보안 태세를 높이는 데 도움이 될 수 있는 추가 방어책이 몇 가지 있다. 이러한 방어책 중 어느 것도 공격자가 토큰을 얻기 위해 새 흐름을 실행할 수 있게 하는 근본 문제를 해결하거나 수정하지 않는다는 점에 유의한다.

6.3.4.2.1. 안전한 토큰 저장

토큰을 직접 처리할 때, 애플리케이션은 접근 토큰과 갱신 토큰을 저장하기 위해 서로 다른 저장 메커니즘을 선택할 수 있다. Local Storage [WebStorage]와 같은 보편적으로 접근 가능한 저장 영역은 Web Worker [WebWorker]와 같은 더 격리된 저장 영역보다 악성 JavaScript에서 접근하기 더 쉽다. 8절은 서로 다른 저장 메커니즘과 그 절충점을 더 자세히 논의한다.

실용적인 구현 패턴은 Web Worker [WebWorker]를 사용하여 갱신 토큰을 격리하고, 리소스 서버로 요청하는 접근 토큰을 애플리케이션에 제공할 수 있다. 이는 공격자가 애플리케이션의 갱신 토큰을 사용하여 새 토큰을 얻는 것을 방지한다.

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

6.3.4.2.2. 송신자 제한 토큰 사용

브라우저 기반 OAuth 클라이언트는 DPoP [RFC9449]를 구현하여 bearer 접근 토큰과 bearer 갱신 토큰에서 송신자 제한 토큰으로 전환할 수 있다. 이러한 구현에서 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절의 정확한 시나리오는 방지하지만, 공격 시나리오의 약간의 변형에는 작동하지 않는다. 예를 들어, 공격자는 팝업 창이나 pop-under 창에서 조용한 흐름을 시작할 수 있다. 또한 브라우저 전용 OAuth 클라이언트는 일반적으로 사용자의 인증 상태를 부트스트랩하기 위해 숨겨진 iframe 기반 흐름에 의존하므로, 이 접근 방식은 사용자 경험에 상당한 영향을 미칠 것이다.

권한 부여 서버는 모든 Authorization Code 흐름에서 사용자 동의를 필수로 만들도록 선택할 수 있다 ([RFC6749]의 10.2절에 설명됨). 이로써 권한 부여 코드를 발급하기 전에 사용자 상호 작용이 요구된다. 이 접근 방식은 공격자가 조용한 흐름을 실행하여 새 토큰 집합을 얻는 것을 더 어렵게 만들 수 있다. 그러나 지속적으로 동의를 요구함으로써 사용자 경험에도 상당한 영향을 미친다. 그 결과 이 접근 방식은 "동의 피로"를 초래하여, 사용자가 공격자가 시작한 흐름과 관련되어 있을 때도 동의를 무심코 승인할 가능성이 높아진다.

6.3.4.3. 요약

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

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

7. 권장되지 않거나 폐기된 아키텍처 패턴

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

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

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

단순한 애플리케이션이 세션 관리 개념을 대체하기 위해 OAuth를 사용함으로써 불필요하게 복잡해지는 경우가 너무 많다. 대표적인 예는 서버 측 API가 뒷받침하는 브라우저 기반 프런트엔드로 이제 구성되는, 서버 측 MVC 애플리케이션의 현대적 형태이다.

이러한 애플리케이션에서 사용자 인증을 전용 제공자에게 맡기기 위해 OpenID connect를 사용하면 애플리케이션의 아키텍처와 개발을 크게 단순화할 수 있다. 그러나 프런트엔드와 백엔드 사이의 접근을 통제하기 위해 OAuth를 사용하는 것은 대개 필요하지 않다. 접근 토큰을 사용하는 대신, 애플리케이션은 전통적인 쿠키 기반 세션 상태에 의존하여 사용자의 인증 상태를 추적할 수 있다. 세션 쿠키를 보호하기 위한 보안 지침은 6.1.3.2절에서 논의된다.

OAuth를 사용하지 말라는 조언이 이 문서에서 어색해 보일 수는 있지만, OAuth는 원래 API에 대한 서드파티 또는 연합 접근을 위해 만들어졌으므로, 단일 공통 도메인 배포에서는 최선의 해결책이 아닐 수 있다는 점에 유의하는 것이 중요하다. 그렇지만 공통 도메인 아키텍처에서도 OAuth를 사용하는 데는 여전히 몇 가지 장점이 있다:

  • 나중에 시스템에 새 도메인을 추가하는 경우처럼 향후 더 많은 유연성을 허용한다. OAuth가 이미 마련되어 있으면 새 도메인을 추가해도 추가적인 재아키텍처가 필요하지 않다.

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

  • 로그인, 다중 인증 지원, 계정 관리, 복구를 애플리케이션 로직의 일부로 만들기보다 OAuth 서버에 중앙 집중화한다.

  • 사용자 인증과 리소스 제공 사이의 책임 분리

제1자 동일 도메인 시나리오에서 브라우저 기반 애플리케이션에 OAuth를 사용하면 이러한 장점을 제공하며, 위에서 설명한 어떤 아키텍처 패턴으로도 달성할 수 있다.

7.1.1. 위협 분석

OAuth를 사용하지 않기 때문에, 이 아키텍처 패턴은 다음 공격 시나리오에만 취약하다: 사용자의 브라우저를 통한 요청 프록시 (5.1.4절). 그 결과, 이 패턴은 다음 결과로 이어질 수 있다: 클라이언트 하이재킹(5.2.3절)

7.2. OAuth Implicit Grant

OAuth 2.0 Implicit grant 유형([RFC6749]의 4.2절에 정의됨)은 권한 부여 서버가 권한 부여 코드 교환 단계 없이 권한 부여 응답(front channel)에서 접근 토큰을 발급하는 방식으로 동작한다. 이 경우 접근 토큰은 redirect URI의 fragment 부분에 반환되며, 공격자에게 접근 토큰을 가로채고 훔칠 여러 기회를 제공한다.

Implicit grant 유형의 보안 속성은 더 이상 권장되는 모범 사례가 아니게 만든다. 이 흐름의 사용을 효과적으로 방지하기 위해, 권한 부여 서버는 권한 부여 응답에서 접근 토큰을 발급해서는 안 되며(MUST NOT), 토큰 엔드포인트에서만 접근 토큰을 발급해야 한다(MUST). 브라우저 기반 클라이언트는 Authorization Code grant 유형을 사용해야 하며(MUST), 접근 토큰을 얻기 위해 Implicit grant 유형을 사용해서는 안 된다(MUST NOT).

7.2.1. 역사적 참고

역사적으로 Implicit grant 유형은 브라우저 기반 애플리케이션에 이점을 제공했다. JavaScript가 페이지 다시 로드를 트리거하지 않고도 URL의 fragment 부분을 항상 임의로 읽고 조작할 수 있었기 때문이다. 이는 앱이 접근 토큰을 얻은 뒤 URL에서 접근 토큰을 제거하기 위해 필요했다. 또한 브라우저에서 CORS가 널리 보급되기 전까지 Implicit grant 유형은 브라우저나 서버에서 CORS 지원을 요구하지 않는 대체 흐름을 제공했다.

현대 브라우저에는 이제 [HTML]의 "Session history and navigation"에 설명된 Session History API가 있으며, 이는 페이지 다시 로드를 트리거하지 않고 URL의 path 및 query string 구성 요소를 수정하는 메커니즘을 제공한다. 또한 CORS는 광범위하게 지원되며 단일 페이지 애플리케이션에서 많은 목적으로 자주 사용된다. 이는 현대적인 브라우저 기반 애플리케이션이 PKCE와 함께 OAuth 2.0 Authorization Code grant 유형을 사용할 수 있음을 의미한다. Session History API 덕분에 페이지 다시 로드를 트리거하지 않고 query string에서 권한 부여 코드를 제거할 수 있으며, 토큰 엔드포인트에서의 CORS 지원 덕분에 권한 부여 서버가 다른 도메인에 있더라도 앱이 토큰을 얻을 수 있기 때문이다.

7.2.2. 위협 분석

이 절에서 논의하는 아키텍처 패턴은 다음 공격 시나리오에 취약하다:

그 결과, 이 패턴은 다음 결과로 이어질 수 있다:

7.2.3. Implicit Grant에 대한 추가 공격

이미 논의한 공격 시나리오와 결과 외에도, Implicit grant 유형의 폐기를 추가로 뒷받침하는 몇 가지 추가 공격이 있다. [RFC6819][RFC9700]의 4.1.2절에 설명된 Implicit grant 유형에 대한 많은 공격에는 충분한 완화 전략이 없다. 다음 절들은 Implicit grant 유형을 계속 사용하면서는 완화할 수 없는 구체적인 공격들을 설명한다.

7.2.3.1. Redirect URI 조작

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

7.2.3.2. 브라우저 기록의 접근 토큰 유출

공격자는 브라우저 기록에서 접근 토큰을 얻을 수 있다. [RFC6819]에서 권장하는 대응책은 토큰에 짧은 만료 시간을 사용하는 것과 브라우저가 응답을 캐시하지 않아야 함을 표시하는 것에 한정된다. 이 중 어느 것도 이 공격을 완전히 방지하지 못하며, 잠재적 피해를 줄일 뿐이다.

또한 많은 브라우저는 이제 브라우저 기록을 클라우드 서비스와 여러 장치에 동기화하므로, URL에서 접근 토큰을 추출할 수 있는 공격 표면이 더욱 넓어진다.

이는 [RFC9700]의 4.3.2절에서 더 자세히 논의된다.

7.2.3.3. 스크립트 조작

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

애플리케이션이 접근 토큰을 얻는 알려진 표준 방식을 사용할 때 페이지에서 악성 스크립트가 실행될 위험은 증폭될 수 있다. 즉, 공격자는 언제나 window.location 변수를 살펴 접근 토큰을 찾을 수 있다. 이 위협 프로필은 Authorization Code 흐름을 통해 얻은 접근 토큰이 어디에 또는 어떻게 저장될 수 있는지를 알고 개별 애플리케이션을 특정해 공격하는 공격자와는 다르다.

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

분석 도구, 충돌 보고, 심지어 소셜 미디어 "like" 버튼과 같은 서드파티 스크립트를 브라우저 기반 애플리케이션에서 사용하는 것은 비교적 일반적이다. 이러한 상황에서 애플리케이션 작성자는 애플리케이션에서 실행되는 코드 전체를 완전히 파악하지 못할 수 있다. 접근 토큰이 fragment에 반환되면, 페이지의 모든 서드파티 스크립트에 보이게 된다.

7.2.4. Implicit Grant의 단점

권장되는 Authorization Code grant 유형을 사용하는 것과 비교해 Implicit grant 유형이 불리한 몇 가지 추가 이유가 있다.

  • OAuth 2.0은 특정 접근 토큰이 해당 클라이언트를 위해 의도되었는지 클라이언트가 검증할 수 있는 메커니즘을 제공하지 않는다. 이는 악의적 당사자가 다른 수단으로 얻은 접근 토큰을 클라이언트에게 넘기는 경우 오용 및 가능한 가장 공격으로 이어질 수 있다.

  • front-channel 리디렉션에서 접근 토큰을 반환하면 이 리디렉션이 실패하거나 가로채일 수 있는 방법이 많기 때문에, 권한 부여 서버는 접근 토큰이 실제로 애플리케이션에 도달할 것이라고 보장할 수 없다.

  • Implicit grant 유형을 지원하려면 추가 코드, 더 많은 유지 관리, 관련 보안 고려 사항에 대한 이해가 필요하다. 권한 부여 서버를 Authorization Code grant 유형만으로 제한하면 구현의 공격 표면이 줄어든다.

  • 브라우저 기반 애플리케이션이 네이티브 앱으로 래핑되는 경우, [RFC8252]도 어쨌든 PKCE와 함께 Authorization Code grant 유형 사용을 요구한다.

7.3. Resource Owner Password Grant

Resource Owner Password Credentials Grant는 [RFC9700]의 2.4절에 설명된 대로 사용해서는 안 된다(MUST NOT). 대신 Authorization Code grant 유형을 사용하고 사용자를 권한 부여 서버로 리디렉션하면, 권한 부여 서버가 사용자에게 안전하고 피싱이 어려운 인증 옵션을 제시하거나, 단일 로그인 세션을 활용하거나, 서드파티 신원 제공자를 사용할 기회를 제공한다. 반대로 Resource Owner Password Credentials Grant는 이러한 것들에 대한 내장 메커니즘을 제공하지 않으며, 대신 사용자 지정 프로토콜로 확장해야 한다.

이 모범 사례를 준수하기 위해, OAuth 또는 OpenID Connect를 사용하는 브라우저 기반 애플리케이션은 이 문서에 설명된 대로 리디렉션 기반 흐름(예: OAuth Authorization Code grant 유형)을 사용해야 한다(MUST).

7.4. Service Worker에서 OAuth 흐름 처리

공격자가 기존 토큰을 추출하거나 새 토큰 집합을 획득하는 능력을 제한하려는 시도로, Service Worker([W3C.service-workers])를 사용하는 패턴이 과거에 제안된 바 있다. 이 패턴에서 애플리케이션이 로드된 뒤 첫 번째로 하는 작업은 Service Worker를 등록하는 것이다. Service Worker는 토큰을 얻기 위해 Authorization Code 흐름을 실행하고, 리소스 서버로 나가는 요청에 적절한 접근 토큰을 추가하는 책임을 진다. 또한 Service Worker는 클라이언트 애플리케이션 코드가 권한 부여 서버의 엔드포인트를 직접 호출하지 못하도록 차단한다. 이 제한은 "새 토큰의 획득 및 추출" 공격 시나리오(5.1.3절)를 대상으로 한다.

아래에 포함된 시퀀스 다이어그램은 클라이언트, Service Worker, 권한 부여 서버, 리소스 서버 사이의 상호 작용을 보여준다.

Service Resource Authorization User Application Worker Server Server browse /authorize redirect w/ authorization code < - - - - - - - - - - - - - - - - - token request w/ auth code /token < - - - - - - - - - - - - - - - - - resource request resource request w/ access token User Application Service Resource Authorization Worker Server Server
그림 4: OAuth 2.0 Service Worker 패턴

이 패턴은 브라우저에서 실행 중인 애플리케이션에 토큰을 절대 노출하지 않는다는 점에 유의한다. Service Worker는 격리된 실행 환경에서 실행되므로 공유 메모리가 없으며, 클라이언트 애플리케이션이 Service Worker의 실행에 영향을 줄 방법이 없다.

7.4.1. 위협 분석

이 절에서 논의하는 아키텍처 패턴은 다음 공격 시나리오에 취약하다:

  • 새 토큰의 획득 및 추출 5.1.3절

  • 사용자의 브라우저를 통한 요청 프록시 5.1.4절

그 결과, 이 패턴은 다음 결과로 이어질 수 있다:

7.4.1.1. Service Worker 공격

Service Worker 사용의 겉보기에 유망한 보안 이점은 그 보안 한계에 대한 더 자세한 논의를 필요로 한다. 관련 공격 시나리오(5.1절)로부터 애플리케이션을 완전히 보호하려면, Service Worker는 두 가지 보안 요구 사항을 충족해야 한다:

  1. 공격자가 토큰을 유출하지 못하게 방지

  2. 공격자가 새 토큰 집합을 획득하지 못하게 방지

등록된 뒤 Service Worker는 Authorization Code 흐름을 실행하고 토큰을 얻는다. Service Worker는 자체 격리된 실행 환경에서 토큰을 추적하므로, 잠재적으로 악성인 코드를 포함한 어떤 애플리케이션 코드도 이에 접근할 수 없다. 결과적으로 Service Worker는 토큰 유출 방지라는 첫 번째 요구 사항을 충족한다. 이는 본질적으로 5.1절에서 논의한 처음 두 공격 시나리오를 무력화한다.

두 번째 보안 요구 사항을 충족하려면, Service Worker는 합법적인 애플리케이션을 제어하는 공격자가 새 Authorization Code grant를 실행할 수 없음을 보장할 수 있어야 한다. 이는 5.1.3절에서 논의한 공격이다. Service Worker의 특성상, 등록된 Service Worker는 이러한 새 흐름을 시작하는 모든 나가는 요청을, 프레임이나 새 창에서 발생하더라도, 차단할 수 있다.

그러나 애플리케이션 내부에서 실행되는 악성 코드는 이 Service Worker를 등록 해제할 수 있다. Service Worker 등록 해제는 애플리케이션에 상당한 기능적 영향을 줄 수 있으므로, 브라우저가 가볍게 처리하는 작업이 아니다. 따라서 등록 해제된 Service Worker는 그렇게 표시되지만, 현재 실행 중인 모든 인스턴스는 해당 browsing context가 종료될 때까지 (예: 탭이나 창을 닫을 때까지) 활성 상태로 남는다. 따라서 공격자가 Service Worker를 등록 해제하더라도, Service Worker는 활성 상태로 남아 공격자가 권한 부여 서버에 도달하지 못하게 방지할 수 있다.

Service Worker를 등록 해제하는 결과 중 하나는 새 browsing context가 열릴 때 Service Worker가 존재하지 않는다는 것이다. 따라서 공격자가 먼저 Service Worker를 등록 해제한 뒤, 프레임에서 새 흐름을 시작하면 해당 프레임의 browsing context와 연결된 Service Worker가 없다. 결과적으로 공격자는 자신의 새 Authorization Code grant를 실행하고, 프레임 URL에서 권한 부여 코드를 추출한 뒤, 이를 토큰으로 교환할 수 있다. 본질적으로 Service Worker는 두 번째 보안 요구 사항을 충족하지 못하며, 공격자가 새 토큰 집합을 획득하는 시나리오(5.1.3절)에 취약한 상태로 남는다.

이러한 단점은 Service Worker를 등록하고 유지 관리하는 상당한 복잡성과 결합되어, 이 패턴이 권장되지 않게 만든다.

마지막으로, Service Worker 사용 자체가 애플리케이션의 공격 표면을 증가시키지는 않는다는 점에 유의한다. 실제로 Service Worker는 기존 애플리케이션에 나가는 요청에 OAuth 접근 토큰을 포함하는 지원을 보강하기 위해 자주 사용된다. 이러한 시나리오의 Service Worker는 애플리케이션의 보안 속성을 변경하지 않으며, 단지 애플리케이션의 개발과 유지 관리를 단순화할 뿐이다.

8. 브라우저의 토큰 저장

브라우저 기반 애플리케이션이 OAuth 접근 토큰 또는 갱신 토큰을 직접 처리할 때, 애플리케이션은 토큰을 일시적으로 또는 영속적으로 저장할 책임을 지게 된다. 그 결과 애플리케이션은 토큰을 관리하는 방법(예: 메모리 내 저장 vs 영속 저장)과 메인 애플리케이션 코드로부터 토큰을 추가로 격리하기 위해 어떤 단계를 취할지 결정해야 한다. 이 절은 몇 가지 서로 다른 저장 메커니즘과 그 속성을 논의한다. 이러한 권고 사항은 OAuth 토큰의 고유한 속성을 고려하며, 그중 일부는 일반적인 브라우저 보안 권고 사항과 겹칠 수 있다.

브라우저 기반 토큰 저장 솔루션의 보안 속성을 논의할 때, 공격자가 브라우저 기반 애플리케이션을 손상시켰을 때의 능력을 이해하는 것이 중요하다. 이전 논의와 유사하게, 두 가지 주요 공격 시나리오를 고려해야 한다:

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

  2. 공격자가 제공자(예: 권한 부여 서버 또는 토큰 중개 백엔드)에서 토큰을 획득함

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

그렇지만 브라우저 기반 저장 솔루션의 서로 다른 보안 속성은 공격자가 저장소에서 기존 토큰을 획득할 수 있는 능력에 영향을 미친다.

8.1. 쿠키

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

쿠키에 대한 헤더 기반 제어 외에도, 브라우저는 쿠키를 가져오고 설정하는 JavaScript Cookie API도 제공한다. 이 Cookie API는 브라우저에 데이터를 저장하는 쉬운 방법으로 오해되는 경우가 많다. 이러한 시나리오에서 JavaScript 코드는 나중에 API 호출의 Authorization 헤더에 포함하기 위해 토큰을 검색하려는 의도로 쿠키에 토큰을 저장한다. 그러나 쿠키가 브라우저 기반 애플리케이션의 도메인과 연결되어 있기 때문에, 브라우저는 이 도메인에서 실행되는 서버로 요청을 보낼 때 토큰이 포함된 쿠키도 함께 보낸다. 이러한 요청의 한 예는 이전에 애플리케이션을 방문한 뒤 브라우저가 애플리케이션을 로드하는 경우(6.3절의 그림에서 A 단계)이다.

JavaScript 기반 저장을 위해 쿠키를 사용할 때 발생하는 이러한 의도하지 않은 부작용 때문에, 이 관행은 권장되지 않는다(NOT RECOMMENDED).

이 관행은 BFF에서 쿠키를 사용하는 것 (6.1.3.2절에서 논의됨)과 다르다는 점에 유의한다. 여기서 쿠키는 JavaScript에서 접근할 수 없으며 백엔드로 전송되도록 의도된다.

8.2. Service Worker의 토큰 저장

Service Worker([W3C.service-workers])는 토큰을 추적할 수 있는 완전히 격리된 환경을 제공한다. 이러한 토큰은 클라이언트 애플리케이션에서 접근할 수 없으며, 유출로부터 효과적으로 보호된다. 이러한 토큰의 보안을 보장하기 위해, Service Worker는 이 토큰을 애플리케이션과 공유할 수 없다. 결과적으로 애플리케이션이 토큰을 사용하는 작업을 수행하려 할 때마다, Service Worker에 해당 작업을 수행하고 결과를 반환하도록 요청해야 한다.

애플리케이션의 실행 컨텍스트로부터 토큰을 격리하려는 경우, Service Worker는 메인 창과 공유되는 어떤 영속 저장 API에도 토큰을 저장해서는 안 된다(MUST NOT). 예를 들어 현재 IndexedDB 저장소는 browsing context와 Service Worker 사이에서 공유되므로, 메인 창에서 접근할 수 없는 상태로 유지되어야 하는 데이터를 Service Worker가 영속화하기에 적합한 위치가 아니다. 결과적으로 Service Worker는 현재 격리된 영속 저장 영역에 접근할 수 없다.

앞서 논의한 것처럼, Service Worker 사용은 공격자가 새 토큰 집합을 얻는 것을 방지하지 못한다. 마찬가지로 애플리케이션이 권한 부여 서버에서 토큰을 얻고 이를 추가 관리를 위해 Service Worker에 전달할 책임을 진다면, 공격자는 합법적인 애플리케이션과 동일한 작업을 수행하여 이러한 토큰을 얻을 수 있다.

8.3. Web Worker의 토큰 저장

애플리케이션은 Web Worker [WebWorker]를 사용할 수 있으며, 이는 Service Worker에 의존하는 앞의 시나리오와 거의 동일한 시나리오를 만든다. Service Worker와 Web Worker의 차이는 접근 수준과 런타임 속성에 있다. Service Worker는 나가는 요청을 가로채고 수정할 수 있는 반면, Web Worker는 단지 백그라운드 작업을 실행하는 방법이다. Web Worker는 일시적이며 browsing context가 닫히면 사라지는 반면, Service Worker는 브라우저에 등록된 영속 서비스이다.

Web Worker를 사용하는 보안 속성은 Service Worker를 사용하는 것과 동일하다. 토큰이 애플리케이션에 노출되면 취약해진다. 토큰을 사용해야 할 때, 그에 의존하는 작업은 Web Worker가 수행해야 한다.

갱신 토큰을 격리하는 한 가지 일반적인 방법은 Web Worker를 사용하는 것이다. 이러한 시나리오에서 애플리케이션은 Web Worker에서 Authorization Code 흐름을 시작한다. 리디렉션에서 온 권한 부여 코드는 Web Worker로 전달되며, Web Worker는 이를 토큰으로 교환한다. Web Worker는 갱신 토큰을 메모리에 보관하고 접근 토큰을 메인 애플리케이션으로 보낸다. 메인 애플리케이션은 원하는 대로 접근 토큰을 사용한다. 애플리케이션이 갱신 토큰 흐름을 실행해야 할 때, Web Worker에 이를 수행하도록 요청하고, 이후 애플리케이션은 새로운 접근 토큰을 얻는다.

이 시나리오에서 애플리케이션 자체의 갱신 토큰은 유출로부터 효과적으로 보호되지만 접근 토큰은 그렇지 않다. 또한 공격자가 새 Authorization Code 흐름을 실행하여 자신의 토큰을 얻는 것을 막을 수 있는 것은 없다(5.1.3절).

8.4. 메모리 내 토큰 저장

또 다른 옵션은 영속 저장소를 사용하지 않고 토큰을 메모리에 보관하는 것이다. 이렇게 하면 토큰 노출을 현재 실행 컨텍스트로만 제한하지만, 페이지 로드 사이에 토큰을 유지할 수 없다는 단점이 있다.

JavaScript 실행 환경에서 메모리 내 토큰 저장의 보안은 closure 변수를 사용하여 토큰을 직접 접근으로부터 효과적으로 보호함으로써 더 향상될 수 있다. closure를 사용하면 토큰은 리소스 서버로 요청을 보내는 함수와 같이 closure 내부의 사전 정의된 함수에서만 접근할 수 있다.

closure는 단순하고 격리된 환경에서는 잘 작동하지만, 브라우저의 실행 환경처럼 복잡한 환경에서는 안전하게 만들기 어렵다. 예를 들어, closure는 작업을 실행하기 위해 toString 함수나 네트워킹 API와 같은 다양한 외부 함수에 의존한다. prototype poisoning을 사용하면 공격자는 이러한 함수를 악성 버전으로 대체하여 closure의 향후 작업이 이러한 악성 버전을 사용하게 만들 수 있다. 악성 함수 내부에서 공격자는 함수 인수에 접근할 수 있으며, 이는 closure 내부의 토큰을 공격자에게 노출할 수 있다.

8.5. 영속 토큰 저장

이 글을 쓰는 시점에 브라우저에서 현재 사용할 수 있는 영속 저장 API는 localStorage([WebStorage]), sessionStorage([WebStorage]), 그리고 [W3C.IndexedDB]이다.

localStorage는 페이지 다시 로드 사이에 지속되며 모든 탭에서 공유된다. 이 저장소는 전체 출처에서 접근 가능하고 장기적으로 지속된다. localStorage는 악성 JavaScript의 무단 접근을 방지하지 못한다. 공격자가 동일한 출처 내에서 코드를 실행하게 되므로 localStorage의 내용을 읽을 수 있기 때문이다. 또한 localStorage는 동기 API이므로 작업이 완료될 때까지 다른 JavaScript를 차단한다.

sessionStorage는 localStorage와 유사하지만, sessionStorage의 수명은 브라우저 탭의 수명과 연결된다. 또한 sessionStorage는 동일한 출처의 페이지가 열린 여러 탭 사이에서 공유되지 않으므로, sessionStorage의 토큰 노출이 약간 줄어든다.

IndexedDB는 localStorage와 같은 영속 저장 메커니즘이지만, 여러 탭뿐 아니라 browsing context와 Service Worker 사이에서도 공유된다. 또한 IndexedDB는 비동기 API이므로 동기 localStorage API보다 선호된다.

이러한 패턴들 사이의 주요 차이는 데이터의 노출이지만, 공격자가 애플리케이션의 실행 환경에서 악성 코드를 실행할 수 있을 때 토큰 유출을 완전히 완화할 수 있는 옵션은 없다는 점에 유의한다.

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

모든 경우에, 이 글을 쓰는 시점에는 브라우저 저장소가 저장 시 암호화된다는 보장이 없다. 이 동작은 디스크의 파일을 읽을 능력이 있는 공격자에게 토큰을 잠재적으로 노출한다. 이러한 공격은 브라우저 기반 애플리케이션의 범위를 훨씬 넘어서는 능력에 의존하지만, 이 주제는 현대 애플리케이션에 대한 중요한 공격 벡터를 강조한다. 점점 더 많은 악성코드가 사용자의 컴퓨터를 크롤링하여 브라우저 프로필을 찾고, 고가치 토큰과 세션 쿠키를 얻도록 특별히 만들어지고 있으며, 이는 계정 탈취 공격으로 이어진다.

브라우저 기반 애플리케이션은 이러한 공격을 완화할 수 없지만, 암호화를 사용해 데이터 기밀성을 보장함으로써 이러한 공격의 결과를 완화할 수 있다. [W3C.WebCryptoAPI]는 JavaScript 코드가 비밀 키를 생성할 수 있는 메커니즘과 그 키를 내보낼 수 없게 하는 옵션을 제공한다. 그런 다음 JavaScript 애플리케이션은 이 API를 사용하여 토큰을 저장하기 전에 암호화하고 복호화할 수 있다. 그러나 [W3C.WebCryptoAPI] 명세는 키가 브라우저 코드로 내보낼 수 없다는 것만 보장하며, 운영 체제에서 키 자체의 기본 저장소에 대해 어떤 요구 사항도 두지 않는다. 따라서 내보낼 수 없는 키를 기본 파일시스템에서의 유출로부터 보호하는 방법으로 의존할 수는 없다.

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

9. 보안 고려 사항

9.1. 토큰의 권한 줄이기

OAuth 세계의 일반적인 보안 모범 사례는 접근 토큰과 연결된 권한을 최소화하는 것이다. 이 모범 사례는 이 명세에서 논의하는 모든 아키텍처에 적용된다. 구체적으로, 다음 고려 사항은 접근 토큰의 권한을 줄이는 데 도움이 될 수 있다:

  • 접근 토큰의 수명을 줄이고, 접근 토큰 갱신에는 갱신 토큰에 의존한다

  • 접근 토큰과 연결된 범위 또는 권한을 줄인다

  • [RFC8707]을 사용하여 접근 토큰을 단일 리소스로 제한한다

OpenID Connect를 사용할 때는 ID Token의 클레임을 통한 민감한 정보 공개를 피하는 것이 중요하다. 권한 부여 서버는 클라이언트가 사용하지 않는 ID token 클레임을 포함하지 않는 것이 좋다(SHOULD NOT).

9.2. 송신자 제한 토큰

이 문서 전반에서 논의한 것처럼, 송신자 제한 토큰의 사용은 브라우저 전용 OAuth 클라이언트의 보안 한계를 해결하지 않는다. 그러나 토큰 중개 백엔드(6.2절) 또는 브라우저 전용 OAuth 클라이언트(6.3절)가 제공하는 보안 수준이 해당 사용 사례에 충분한 경우, 송신자 제한 토큰은 접근 토큰과 갱신 토큰 모두의 보안을 향상하는 데 사용할 수 있다. 브라우저 기반 애플리케이션에서 사용할 수 있는 방식으로 송신자 제한 토큰을 구현하는 한 가지 방법은 DPoP [RFC9449]이다.

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

애플리케이션이 내보낼 수 없는 키를 생성하는 API를 사용할 수 없다면, 애플리케이션은 자체 실행 컨텍스트에서 개인 키를 격리하기 위한 조치를 취해야 한다. 그렇게 하는 기법은 8절에서 논의한 것처럼 안전한 토큰 저장 메커니즘을 사용하는 것과 유사하다.

내보낼 수 없는 키는 JavaScript 컨텍스트 내부에서의 유출로부터 보호되지만, 기본 개인 키가 파일시스템에서 유출되는 것은 여전히 잠재적인 공격 벡터이다. 이 글을 쓰는 시점에, [W3C.WebCryptoAPI]는 내보낼 수 없는 키가 실제로 Trusted Platform Module (TPM)에 의해 보호되거나 디스크에 암호화된 형태로 저장된다는 보장을 하지 않는다. 공격자가 예를 들어 악성코드를 통해 사용자의 컴퓨터 파일시스템에 접근할 수 있다면, 기본 파일시스템에서 내보낼 수 없는 키가 유출될 가능성이 여전히 있다. 이는 잠재적인 공격 벡터를 사실상 세션 하이재킹 공격과 동등하게 만든다.

9.3. 권한 부여 서버 Mix-Up 완화

권한 부여 서버 mix-up 공격은 최소 두 개의 권한 부여 서버를 지원하는 모든 클라이언트에 심각한 위협이 된다. [RFC9700]의 4.4절은 mix-up 공격과 위에서 언급한 대응책에 대한 추가 세부 사항을 제공한다.

9.4. Origins를 사용한 애플리케이션 격리

웹의 많은 보안 메커니즘은 origins에 의존하며, origins는 <scheme, hostname, port>라는 삼중 값으로 정의된다. 예를 들어, 브라우저는 서로 다른 origins의 browsing contexts를 자동으로 격리하고, 리소스를 특정 origins로 제한하며, 나가는 cross-origin 요청에 CORS 제한을 적용한다.

따라서 단일 origin에 둘 이상의 애플리케이션을 배포하지 않는 것이 모범 사례로 간주된다. 단일 origin에 하나의 애플리케이션만 배포하는 아키텍처는 이러한 브라우저 제한을 활용하여 애플리케이션의 보안을 높일 수 있다. 또한 애플리케이션당 하나의 origin을 가지면 CORS, CSP 등과 같은 보안 조치를 구성하고 배포하기가 더 쉬워진다.

10. IANA 고려 사항

이 문서는 어떤 IANA 조치도 요구하지 않는다.

11. 참고 문헌

11.1. 규범적 참고 문헌

[Fetch]
whatwg, "Fetch", , <https://fetch.spec.whatwg.org/>.
[I-D.ietf-httpbis-rfc6265bis]
Bingler, S., West, M., and J. Wilander, "Cookies: HTTP State Management Mechanism", 진행 중인 작업, Internet-Draft, draft-ietf-httpbis-rfc6265bis-22, , <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22>.
[RFC2119]
Bradner, S., "요구 수준을 나타내기 위해 RFC에서 사용하는 핵심 단어", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC6749]
Hardt, D., Ed., "OAuth 2.0 Authorization Framework", RFC 6749, DOI 10.17487/RFC6749, , <https://www.rfc-editor.org/info/rfc6749>.
[RFC6750]
Jones, M. and D. Hardt, "OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, DOI 10.17487/RFC6750, , <https://www.rfc-editor.org/info/rfc6750>.
[RFC7636]
Sakimura, N., Ed., Bradley, J., and N. Agarwal, "OAuth 공개 클라이언트를 위한 Proof Key for Code Exchange", 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. and 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., and H. Tschofenig, "OAuth 2.0을 위한 Resource Indicators", RFC 8707, DOI 10.17487/RFC8707, , <https://www.rfc-editor.org/info/rfc8707>.
[RFC9449]
Fett, D., Campbell, B., Bradley, J., Lodderstedt, T., Jones, M., and D. Waite, "OAuth 2.0 Demonstrating Proof of Possession (DPoP)", RFC 9449, DOI 10.17487/RFC9449, , <https://www.rfc-editor.org/info/rfc9449>.
[RFC9700]
Lodderstedt, T., Bradley, J., Labunets, A., and D. Fett, "OAuth 2.0 보안을 위한 Best Current Practice", BCP 240, RFC 9700, DOI 10.17487/RFC9700, , <https://www.rfc-editor.org/info/rfc9700>.
[W3C.service-workers]
"Service Workers", W3C CR service-workers, W3C service-workers, <https://www.w3.org/TR/service-workers/>.
[WebMessaging]
whatwg, "HTML - Cross-document messaging", , <https://html.spec.whatwg.org/#web-messaging>.

11.2. 정보성 참고 문헌

[CryptoKeyPair]
Contributors, M., "CryptoKeyPair", 날짜 없음, <https://developer.mozilla.org/en-US/docs/Web/API/CryptoKeyPair>.
[HTML]
whatwg, "HTML", , <https://html.spec.whatwg.org/>.
[OpenID]
Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., and C. Mortimore, "OpenID Connect Core 1.0 incorporating errata set 2", , <https://openid.net/specs/openid-connect-core-1_0-errata2.html>.
[OWASPCheatSheet]
"OWASP Cheat Sheet", 날짜 없음, <https://cheatsheetseries.owasp.org/>.
[RFC6819]
Lodderstedt, T., Ed., McGloin, M., and P. Hunt, "OAuth 2.0 Threat Model and Security Considerations", RFC 6819, DOI 10.17487/RFC6819, , <https://www.rfc-editor.org/info/rfc6819>.
[SessionFixation]
"Session Fixation", 날짜 없음, <https://owasp.org/www-community/attacks/Session_fixation>.
[Site]
Contributors, M., "Site", 날짜 없음, <https://developer.mozilla.org/en-US/docs/Glossary/Site>.
[W3C.CSP3]
"Content Security Policy Level 3", W3C WD CSP3, W3C CSP3, <https://www.w3.org/TR/CSP3/>.
[W3C.IndexedDB]
"Indexed Database API", W3C REC IndexedDB, W3C IndexedDB, <https://www.w3.org/TR/IndexedDB/>.
[W3C.SRI]
"Subresource Integrity", W3C REC SRI, W3C SRI, <https://www.w3.org/TR/SRI/>.
[W3C.wasm-core-2]
"WebAssembly Core Specification", W3C CR wasm-core-2, W3C wasm-core-2, <https://www.w3.org/TR/wasm-core-2/>.
[W3C.WebCryptoAPI]
"Web Cryptography API", W3C REC WebCryptoAPI, W3C WebCryptoAPI, <https://www.w3.org/TR/WebCryptoAPI/>.
[WebStorage]
whatwg, "HTML Living Standard - Web Storage", , <https://html.spec.whatwg.org/#webstorage>.
[WebWorker]
whatwg, "HTML Living Standard - Web workers", , <https://html.spec.whatwg.org/#toc-workers>.

부록 A. 문서 이력

[[ 최종 명세에서 제거될 예정 ]]

-26

-25

-24

-23

-22

-21

-20

-19

-18

-17

-16

-15

-14

-13

-12

-11

-10

-09

-08

-07

-06

-05

-04

-03

-02

-01

부록 B. 감사의 글

저자들은 네이티브 애플리케이션에 대한 권고가 브라우저 기반 애플리케이션의 많은 모범 사례에 참고가 된 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, Eric Vyncke, Erik Kline, 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, and Yannick Majoros.

저자 주소

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