[OAuth 2.0] 9. Native Applications

네이티브 애플리케이션은 리소스 소유자가 사용하는 장치에 설치되고 실행되는 클라이언트(즉, 데스크톱 애플리케이션, 네이티브 모바일 애플리케이션)를 의미한다. 네이티브 애플리케이션은 보안, 플랫폼 기능, 그리고 전반적인 최종 사용자 경험과 관련하여 특별한 고려가 필요하다.

 

인가 엔드포인트는 클라이언트와 리소스 소유자의 사용자 에이전트 간의 상호작용을 요구한다. 네이티브 애플리케이션은

  • 외부 사용자 에이전트를 호출하거나, 애플리케이션 내부에 사용자 에이전트를 임베드할 수 있다.

예를 들면 다음과 같다

  • 외부 사용자 에이전트(External user-agent)
    • 네이티브 애플리케이션은 운영체제에 등록된 스킴을 가진 리다이렉션 URI를 사용하여 클라이언트를 처리자로 호출함으로써 인가 서버의 응답을 수신할 수 있다. 또한 자격 증명의 수동 복사-붙여넣기, 로컬 웹 서버 실행, 사용자 에이전트 확장 설치, 또는 클라이언트가 제어하는 서버에 호스팅된 리소스를 식별하는 리다이렉션 URI를 제공하고, 해당 서버가 다시 네이티브 애플리케이션이 사용할 수 있도록 응답을 전달하는 방식도 사용할 수 있다.
  • 임베디드 사용자 에이전트(Embedded user-agent)
    • 네이티브 애플리케이션은 리소스 로딩 중에 발생하는 상태 변화를 모니터링하거나, 사용자 에이전트의 쿠키 저장소에 접근함으로써, 임베디드된 사용자 에이전트와 직접 통신하여 응답을 획득한다.
이 문단은 네이티브 앱에서 OAuth 인가를 어떻게 사용자에게 시키느냐를 보고 있다.

네이티브 애플리케이션이란 -> 스마트폰 앱, 데스크톱 앱 -> 브라우저 안에서 도는 웹앱 X
- 이 앱들은 client_secret을 안전하게 숨길 수 있음
- 그래서 OAuth에서 특별 취급 대상임

"인가 엔드 포인트는 사용자 에이전트가 필요하다"는 뜻
- OAuth 인가는 항상 사용자가 직접 로그인하고, 동의 화면을 봐야함 -> ID/PW를 앱이 직접 받으면 안 됨.
그래서 반드시 사용자 에이전드(= 브라우저)를 통해 인가 서버와 상호작용을 해야함 

여기서 선택지는 두 개로 나뉨
A - 외부 사용자 에이전트
B - 임베디드 사용자 에이전트

A -> 폰의 브라우저를 여는 방식
- 카카오 로그인 Android 앱 예시
1. 카카오 사용자가 앱에서 -> 카카오로 로그인 버튼 클릭
2. 앱이 외부 브라우저(Chrome, Safari)를 염
https://auth.kakao.com/oauth/authorize?client_id=...​
3. 사용자 브라우저에서
- 카카오 로그인 
- 동의 화면 성공 후

4. 로그인 성공 후
myapp://callback?code=ABC123​

 

 

5. 브라우저가 OS에게 말함 "myapp:// 이 스킴 처리할 앱 있어?
6. OS가 네이티브 앱 실행(아 myapp 스킴은 이 앱이 처리하도록 등록돼 있음)
7. 앱이 code=ABC123 받음

이게 문서에서 말한 이 부분 “운영체제에 등록된 스킴을 가진 리다이렉션 URI를 사용하여 클라이언트를 처리자로 호출”
- Android: myapp://callback
- iOS: myapp://oauth
- Windows: myapp://auth
이건 네이티브 앱 개발자가 운영체제에 수동으로 등록한 것임 

Android(AndroidManifest.xml)
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data
            android:scheme="myapp"
            android:host="callback" />
    </intent-filter>
</activity>​

 

등록하면
myapp://callback?code=ABC​
이 URL이 열릴 때 -> Android OS가 아 이건 myapp 스킴이네? 이 앱 실행

임베디드 사용자 에이전트란 -> 네이티브 앱 내부에 포함된 브라우저를 의미
예 Android -> WebView, IOS : WKWebView
즉 크롬/사파리 같은 외부 브라우저를 띄우는 게 아니라, 앱 화면 안에서 로그인 페이지를 띄우는 방식

"네이티브 애플리케이션은 리소스 로딩 중에 발생하는 상태 변화를 모니터링하거나, 사용자 에이전트의 쿠키 저장소에 접근함으로써, 임베디드된 사용자 에이전트와 직접 통신하여 응답을 획득한다."
1. 리소스 로딩 중에 발생하는 상태 변화
-> 웹뷰가 어떤 URL을 로딩하는지 감시한다는 뜻

https://auth.example.com/login
→ https://auth.example.com/authorize
→ myapp://callback?code=ABC​
앱은 WebView에서 URL이 바뀌는 순간을 감시하고, code=ABC 같은 값을 직접 가로챔 
-> 브라우저 OS -> 앱 이런 흐름 없음

2. 사용자 에이전트의 쿠키 저장소에 접근
-> 웹뷰의 쿠기를 앱이 직접 볼 수 있다는 뜻
즉, 로그인 세션 쿠키, 인증 상태, 토큰 관련 쿠키
를 앱이 직접 읽거나 조작 가능 but 이게 바로 보안 이슈의 핵심 포인트

3. 직접 통신하여 응답을 획득
-> 리다이렉션을 기다리지 않고
-> 웹뷰 내부에서 바로 결과를 가져온다.

즉,
외부 사용자 에이전트 : redirect -> OS -> 앱
임베디드 사용자 에이전트 : WebView 안에서 직접 감지

실제 예시
1. 앱 화면에 WebView 띄움
2. WebView에서 로그인 페이지 표시
3. 사용자가 ID/비밀번호 입력
4. 인가 서버가 리다이렉트
https://client.example.com/callback?code=ABC​

5. 앱이 WebView의 URL 변경 감지
6. code=ABC 추출
7. WebView 닫고 토큰 요청
-> OS 개입 없음
-> 브라우저도 앱 소유

 

외부 사용자 에이전트와 임베디드 사용자 에이전트 중 하나를 선택할 때, 개발자는 다음 사항들을 고려해야 한다:

  • 외부 사용자 에이전트는 리소스 소유자가 이미 인가 서버에 대해 활성 세션을 가지고 있을 수 있으므로, 재인증이 필요 없어 완료율을 향상시킬 수 있다. 또한 익숙한 최종 사용자 경험과 기능을 제공한다. 리소스 소유자는 인증을 보조하기 위해 사용자 에이전트의 기능이나 확장(예: 비밀번호 관리자, 2단계 인증 장치 판독기)에 의존할 수도 있다.
  • 임베디드 사용자 에이전트는 컨텍스트를 전환하거나 새로운 창을 열 필요가 없으므로 사용성을 향상시킬 수 있다.
  • 임베디드 사용자 에이전트는 보안상 도전을 야기하는데, 이는 리소스 소유자가 대부분의 외부 사용자 에이전트에서 제공되는 시각적 보호 장치에 접근할 수 없는, 식별되지 않은 창에서 인증을 수행하기 때문이다. 임베디드 사용자 에이전트는 최종 사용자가 식별되지 않은 인증 요청을 신뢰하도록 학습시키며, 이는 피싱 공격을 더 쉽게 실행할 수 있게 만든다.
"외부 사용자 에이전트는 리소스 소유자가 이미 인가 서버에 대해 활성 세션을 가지고 있을 수 있으므로, 재인증이 필요 없어 완료율을 향상시킬 수 있다."
- 사용자는 이미 크롬/사파리에서 Google/Kakao/Github 등에 로그인된 상태 일 수 있음 그러면 OAuth 인증 시 ID/비밀번호 다시 안 쳐도 됨 "이 앱에 권한 줄까요?만 누르면 끝 -> 로그인 성공률이 높아진다.

"익숙한 최종 사용자 경험과 기능을 제공한다."
- 사용자는 자기가 매일 쓰는 브라우저 UI를 봄, 주소창, HTTPS, 도메인 확인 가능 -> 아 이거 진짜 카카오 로그인 맞네 라고 인지 가능

"사용자 에이전트의 기능이나 확장(예: 비밀번호 관리자, 2단계 인증 장치 판독기)에 의존할 수도 있다."
- 외부 브라우저에서는 비밀번호 관리자, OTP/FIDO/보안키, 자동 완성, 브라우저 확장 -> 보안 + 편의성 둘다 상승

임베디드 사용자 에이전트의 장점
"임베디드 사용자 에이전트는 컨텍스트를 전환하거나 새로운 창을 열 필요가 없으므로 사용성을 향상시킬 수 있다."
- 앱 화면 안에서 로그인 -> 승인 -> 완료
- 브라우저로 튕기지 않음, UX는 깔끔함

예시 -> 앱 안 WebView, 화면 전환 없이 로그인

하지만 RFC가 가장 경고하는 부분!!!!
"임베디드 사용자 에이전트는 보안상 도전을 야기한다"
- 이유 1 : 식별되지 않은 창 -> WebView에는 주소창 없음, 인증서 정보 없음, 진짜 사이트인지 확인 불가 -> 사용자는 이게 진짜 카카오인지, 앱이 흉내 낸 건지 구분 불가
- 이유 2 : 시각적 보호 장치가 없음 -> 외부 브라우저에는 HTTPS 표시, 도메인 표시, 피싱 경고 임베디드 WebView에는 이런 보호 장치 없음
- 이유 3 : 사용자를 나쁜 습관으로 교육 -> 사용자가 "아 그냥 앱 안에서 로그인 치는 게 정상인가 보다"라고 학습이 됨 그러면 피싱 앱도 같은 UI로 로그인 요구 가능



암묵적 그랜트 유형과 인가 코드 그랜트 유형 중 하나를 선택할 때는 다음 사항들을 고려해야 한다:

  • 인가 코드 그랜트 유형을 사용하는 네이티브 애플리케이션은, 네이티브 애플리케이션이 클라이언트 자격 증명을 기밀로 유지할 수 없기 때문에, 클라이언트 자격 증명을 사용하지 않고 이를 사용하는 것이 바람직하다(SHOULD).
  • 암묵적 그랜트 유형 흐름을 사용하는 경우, 리프레시 토큰이 반환되지 않으므로, 액세스 토큰이 만료되면 인가 과정을 다시 반복해야 한다.
이 문장이 말하는 핵심이
- 네이티브 앱은 client_secret을 안전하게 숨길 수 없다. 

그래서 인가 코드 프로우는 쓰되 client_id만 사용하고 client_secret은 쓰지 마라 라는 뜻

왜 네이티브 앱은 client_secret을 숨길 수 없을까?
이유는 앱 바이너리는 디컴파일 가능, 리버스 엔지니어링 가능 즉, APK/IPA 안에 넣은 client_secret은 누구나 추출 가능 비밀이 아님

그래서 네이티브 앱은 client_secret은 사용하지 말라는 거

실제 예시
1. 앱 → 브라우저 열기 (인가 요청)
2. 인가 서버 → code 발급
3. 앱 → token endpoint 요청 - client_id - code - code_verifier (PKCE)   client_secret 없음


(※ PKCE는 RFC 7636에서 추가된 보완 기법)
Authorization Code Flow
+ PKCE
- client_secret​


암묵적 그랜트 유형
- 이 방식의 치명적인 단점은 refresh_token이 없다
결과적으로 생기는 문제는 access_token이 만료되었을 때 재발급 불가라서 해결 방법은 다시 로그인이다 -> UX 매우 나쁨

실제 예시
1. 로그인 → access_token 받음 (1시간)
2. 1시간 후 만료
3. API 호출 실패
4. 다시 OAuth 로그인 화면으로 이동