■ MCP 인증·인가
표시가 없는 문장은 공식 문서에서 확인한 내용이에요. 다른 표시는 읽는 법
왜 배우나 — 시험이 요구하는 것
시험 목표 3.2 인증·권한 빈틈 · D3 연결 19%Analyze authentication and authorization requirements to identify security gaps
이 개념으로 푸는 판단 (할 수 있어야 하는 것)
- 3.2① 같은 머신의 stdio MCP 서버와 원격 HTTP MCP 서버를 함께 붙인다 — 자격증명을 각각 어떻게 주나? — 전송 방식별 인증 고르기
- 3.2② 사내 MCP 서버가 받은 사용자 토큰을 Salesforce API 호출에 그대로 붙인다 — 무엇이 빈틈인가? — 토큰 경계 (audience 검증 · 그대로 넘기기 금지)
- 7.1③ 에이전트에 줄 권한, 어디까지? — 행동을 allow/ask/deny 로 나눈 공유 설정 + MCP 자격증명 최소 권한
한눈에
MCP 에서 인가(authorization)는 **선택(OPTIONAL)** 이다. 붙인다면 전송 방식이 방법을 정한다. stdio 는 환경에서 자격증명을 받는다. HTTP 는 OAuth 2.1 기반 규격을 따른다
HTTP 인가에서 역할은 셋이다
- MCP 서버 : OAuth resource server
- MCP 클라이언트 : OAuth client
- 토큰 발급 : authorization server
비유 같은 파드 사이드카는 마운트된 시크릿·환경변수를 쓴다. 외부 API 는 OIDC/OAuth 토큰을 쓴다. 게이트웨이가 받은 JWT 를 제3자 API 에 재사용하지 않는 것과 같다
ELI5
놀이공원 손목 띠는 그 놀이공원에서만 써요.
- 집 안 방끼리는 띠 없이 열쇠를 서랍에 두고 써요(로컬).
- 밖에 나갈 땐 입구에서 띠를 받아요(원격).
- 옆 놀이공원 입구에 내 띠를 내밀면 안 돼요 — 거기서 새 띠를 받아요.
- 처음엔 작은 띠만 받고, 큰 놀이기구 앞에서 더 받아요.
개념 설명
원리
인가(authorization)는 선택이다. 붙이면 전송이 방법을 정한다
- stdio 서버는 클라이언트가 띄운 같은 머신의 자식 프로세스다. 그래서 네트워크 경계가 없다. 자격증명은 환경(환경 변수)으로 주면 된다. 규격에는 OAuth 규격을 따르지 말라고(SHOULD NOT) 적혀 있다.
- HTTP 서버는 네트워크 너머에 있다. 그래서 OAuth 2.1 을 쓴다. 역할은 셋이다.
- MCP 서버 : resource server(토큰을 검사한다)
- 클라이언트 : OAuth client(토큰을 받아 온다)
- authorization server : 토큰을 발급한다
- 인가 자체가 OPTIONAL 이다. 인증(authentication) 없는 공개 서버도 규격 위반은 아니다.
토큰은 한 곳 전용 (audience)
- 클라이언트는 토큰을 받을 때
resource파라미터로 "이 서버용"을 박는다. - 서버는 자기용이 아닌 토큰은 거부한다. 그래야 다른 서비스용 토큰을 주워 와 재사용하는 공격이 막힌다.
- 서버가 뒤에서 제3자 API 를 부르면, 서버는 그 API 의 OAuth client 가 되어 새 토큰을 받는다. 받은 토큰을 그대로 넘기면(passthrough) 업스트림이 "MCP 서버가 검증했겠지"라고 믿어 버린다. 그러면 통제(control)·감사·신뢰 경계가 다 깨진다.
scope 는 점진적으로 올린다
- 처음에는 읽기 수준 최소 scope 를 받는다. 권한이 더 필요한 작업을 처음 하려 할 때 서버가
WWW-Authenticate로 필요한 scope 를 알려 준다. 그때 올린다.
규칙과 판단
갈림길
① 전송으로 가르기 [3.2①]
환경에서
자격증명은 환경(환경 변수)에서 받는다. 인가 규격은 따르지 않는다 (SHOULD NOT)
OAuth 2.1
OAuth 2.1 흐름으로 받은 액세스 토큰을 쓴다. 규격을 따른다 (SHOULD)
그 프로토콜 보안 관행
그 밖 전송은 그 프로토콜의 보안 관행을 따른다 (MUST)
②③ 토큰 경계 [3.2②]
resource 파라미터
클라이언트는 토큰을 요청할 때 resource 파라미터(RFC 8707)로 대상 서버를 박는다. 그래서 다른 서비스에 못 쓴다
받는다
서버는 자기 앞으로 발급된(audience 에 자기가 든) 토큰만 받는다
거부한다
나머지는 거부한다. 무효·만료 토큰에는 HTTP 401 을 준다
따로 발급받은 토큰
서버가 그쪽의 OAuth client 가 되어 따로 발급받은 토큰을 쓴다. 받은 토큰을 그대로 넘기기(token passthrough)는 금지다 (MUST NOT). 넘기면 업스트림의 rate limit·요청 검증이 우회된다. 로그에 누가 불렀는지 안 남는다
④ scope 크기 [3.2②]
최소 scope
처음에는 읽기·탐색용 최소 scope 를 받는다
요구(scope challenge)하면 올린다
서버가 WWW-Authenticate 로 필요한 scope 를 요구(scope challenge)하면 올린다. files:*·admin:* 를 한 번에 받으면 토큰 탈취 피해가 커진다
흐름
그림 속 이름·금액·시간·토큰 수는 예시예요. 공식 한도·동작은 카드 본문 값이 기준.
장면 글로 보기
칸 : MCP 클라이언트 → MCP 서버 → 업스트림
블록 : 액세스 토큰 (바뀌는 것)
규칙 (MCP 서버) : 자기 앞으로 발급된(audience 에 자기가 든) 토큰만
질문 : 서버가 받은 토큰을 업스트림에 내밀어도 될까?
① 따로 발급받는다 ✓
- 시작 : MCP 클라이언트 — 쪽지 : 토큰 요청 / resource= / https://mcp.shop.example
- 요청 : 액세스 토큰 → MCP 서버 :
resource(RFC 8707)로 대상을 박는다 — 쪽지 : Authorization: / Bearer eyJ… / aud: mcp.shop.example - 검사 : MCP 서버 : audience 에 자기가 든 토큰만 받는다 — 쪽지 : aud 일치 → 받음
- 업스트림 호출 : MCP 서버 → 업스트림 : 따로 발급받은 토큰을 쓴다 — 쪽지 : Authorization: / Bearer sf_9x… / (Salesforce 가 발급)
- 끝 : 서버가 그쪽의 OAuth client 가 되어 따로 발급받은 토큰으로 부른다
② 그대로 넘긴다 ≠
- 시작 : MCP 클라이언트 — 쪽지 : 토큰 요청 / resource= / https://mcp.shop.example
- 요청 : 액세스 토큰 → MCP 서버 : 토큰을 받는다 — 쪽지 : Authorization: / Bearer eyJ… / aud: mcp.shop.example
- 검사 : MCP 서버 : audience 에 자기가 든 토큰만 받는다 — 쪽지 : aud 일치 → 받음
- 넘기기 : MCP 서버 → 업스트림 : 그대로 넘긴다(passthrough) — 쪽지 : Authorization: / Bearer eyJ… / (받은 그 토큰)
- 끝 : 받은 토큰을 그대로 넘기면 업스트림의 rate limit·요청 검증이 우회된다. 로그에 누가 불렀는지 안 남는다
나머지 규칙
-
그 밖의 필수
- PKCE(S256)를 쓴다
- 토큰은
Authorization헤더로 보낸다(URL 쿼리 금지) - 짧은 수명 토큰을 권장한다
- 공개 클라이언트는 refresh 토큰을 회전한다
-
Claude API 쪽
- MCP connector :
authorization_token에 액세스 토큰을 넣는다. OAuth 흐름과 갱신은 호출 전에 우리가 처리한다. 자세한 것은 ■ MCP connector 에 있다 - Claude API 호출 자체 :
Authorization: Bearer <API 키 또는 짧은 수명 토큰>을 쓴다.x-api-key는 레거시 대체 수단(legacy fallback)으로 아직 지원된다. 짧은 수명 토큰은 Workload Identity Federation(2026-05-04 출시)이다 - 에이전트가 키를 아예 못 보게 하려면 프록시가 주입한다. 자세한 것은 ■ 프롬프트 인젝션 피해 경로 끊기 에 있다
- MCP connector :
규칙 원문 ①②③④ (갈림길로 그린 규칙)
-
전송별로 다르다 [3.2①]
전송 자격증명 규격 말 stdio (같은 머신 프로세스) 환경(환경 변수)에서 받는다 인가 규격을 따르지 않는다 (SHOULD NOT) HTTP (Streamable HTTP 등) OAuth 2.1 흐름으로 받은 액세스 토큰 규격을 따른다 (SHOULD) 그 밖 그 프로토콜 보안 관행 MUST -
토큰 경계는 audience 다 [3.2②]
- 서버는 자기 앞으로 발급된(audience 에 자기가 든) 토큰만 받고 나머지는 거부한다. 무효·만료 토큰에는 HTTP 401 을 준다
- 클라이언트는 토큰을 요청할 때
resource파라미터(RFC 8707)로 대상 서버를 박는다. 그래서 다른 서비스에 못 쓴다 - 업스트림(Salesforce 등)을 부를 때는 서버가 그쪽의 OAuth client 가 되어 **따로 발급받은 토큰**을 쓴다. 받은 토큰을 그대로 넘기기(token passthrough)는 금지다 (MUST NOT)
-
passthrough 가 왜 나쁜가 : 까닭은 넷이다
- 업스트림의 rate limit·요청 검증이 우회된다
- 로그에 누가 불렀는지 안 남는다
- 훔친 토큰으로 서버를 데이터 유출 통로로 쓴다
- 한 서비스가 뚫리면 옆 서비스까지 뚫린다
-
scope 도 최소부터 받는다. 처음에는 읽기·탐색용 최소 scope 를 받는다. 권한이 필요한 작업을 처음 할 때 서버가
WWW-Authenticate로 필요한 scope 를 요구(scope challenge)하면 올린다files:*·admin:* 를 한 번에 받으면 토큰 탈취 피해가 커진다. 자세한 것은 ■ 최소 권한 에 있다
더 깊이 : 판단 규칙 · 상황 변수 (설명)
판단 규칙
| 조건 | 답 | 근거 |
|---|---|---|
| stdio 서버에 자격증명을 준다 | 환경(환경 변수)에서 준다 | authorization |
| HTTP 서버에 인가를 붙인다 | 이 규격(OAuth 2.1 기반)을 따른다 | authorization |
| 인가를 꼭 붙여야 하나 | 선택(OPTIONAL)이다 | authorization |
| 서버가 받은 토큰의 audience 가 자기가 아니다 | 거부한다 (무효면 401) | authorization |
| 서버가 업스트림 API 를 부른다 | 업스트림용 토큰을 따로 받는다. 받은 토큰을 그대로 넘기지 않는다 | authorization · security best practices |
| 클라이언트가 토큰을 요청한다 | resource 파라미터로 대상 서버를 명시한다 | authorization |
| scope 범위를 정한다 | 최소부터 받는다. 서버가 필요한 scope 를 요구(scope challenge)할 때 올린다 | security best practices |
| 토큰을 전달한다 | Authorization 헤더로 보낸다. URL 쿼리는 금지다 | authorization |
| connector 로 인증 서버에 붙는다 | 우리가 OAuth 흐름을 먼저 돌려 authorization_ 에 넣는다 | mcp-connector |
상황 변수
| 변수 | 값 | 답 쪽 | 근거 |
|---|---|---|---|
| 전송 | stdio | 환경 변수를 쓴다 | 판단 규칙 1 |
| 전송 | Streamable HTTP | OAuth 2.1 을 쓴다 (붙일 때) | 판단 규칙 2 |
| 서버 성격 | 공개 읽기 전용 문서 서버 | 인가가 없어도 규격 위반이 아니다 | authorization |
| 서버 성격 | 사용자 데이터를 다룬다 | OAuth 와 audience 검증을 쓴다 | authorization |
| 업스트림 | 있다(제3자 API) | 서버가 별도 OAuth client 가 된다 | authorization |
| 서버 역할 | 정적 client ID 로 제3자 인가 서버에 중계하는 프록시다 | 동적 등록 클라이언트마다 사용자 동의를 받는다 | authorization |
| 누가 흐름을 돌리나 | MCP connector 를 거친다 | 우리 앱이 돌린다 (호출 전 토큰 확보·갱신) | mcp-connector |
| 에이전트가 키를 봐도 되나 | 안 된다(인젝션 위험) | 경계 밖 프록시·MCP 서버가 주입한다 | secure-deployment · ■ 프롬프트 인젝션 피해 경로 끊기 |
연습 문제
사내 MCP 서버(Streamable HTTP)가 상담원 앱이 보낸 액세스 토큰을 검사한 뒤, 뒤에서 Salesforce API 를 불러 고객 기록을 가져온다업스트림 호출. 개발자는 "받은 토큰을 그대로 넘기면 사용자 권한이 유지돼 안전하다"고 한다. Salesforce 호출에는 어떤 토큰을 쓸까?
시험 대비
함정
- "stdio 서버에도 OAuth 2.1 흐름을 붙인다" 는 함정이다. 규격은 SHOULD NOT 이다. 자격증명은 환경에서 받는다
- "원격 MCP 는 반드시(MUST) OAuth 를 쓴다" 는 함정이다. 인가 자체가 OPTIONAL 이다. HTTP 면 SHOULD 다
- "로컬 프로세스는 API 키 경계다" (영상 표현) 는 함정이다. 공식은 "환경에서 자격증명" 이라고 한다
- "토큰을 그대로 넘기면 사용자 권한이 유지돼 오히려 안전하다(SSO 처럼)" 는 함정이다. 규격이 명시적으로 금지한다
- 용어 : 규격의 'Confused Deputy' 절은 좁게는 정적 client ID 를 쓰는 프록시 서버의 동의 문제다(동적 등록 클라이언트마다 사용자 동의 필수). 토큰 그대로 넘기기는 별도 항목이다. 다만 규격은 그것도 confused deputy 를 부를 수 있다고 적는다
- "scope 를 한 번에 넉넉히 받아 동의창을 줄인다" 는 함정이다. 과한 scope 는 피해 반경을 키우고, 동의 포기도 부른다. scope 는 점진적으로 올린다
신호
"Authorization is OPTIONAL for MCP implementations."
"Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment."
"Implementations using an HTTP-based transport SHOULD conform to this specification."
"MCP servers MUST only accept tokens specifically intended for themselves and MUST reject tokens that do not include them in the audience claim"
"The MCP server MUST NOT pass through the token it received from the MCP client."
"Token passthrough is explicitly forbidden in the authorization specification"
더 깊이 : 뒤집히는 때 · 오답 재료 (설명)
뒤집히는 때
1. "원격이니 OAuth 필수"가 틀리는 때
- 조건 : 사내망 안의 공개 사내 문서 검색 서버다(Streamable HTTP). 사용자별 데이터는 없다.
- 판단 : 인가는 OPTIONAL 이다. 붙인다면 이 규격을 따른다(SHOULD). "MUST OAuth" 라고 쓴 보기는 과장이다.
- 근거 : authorization
2. stdio 인데 OAuth 가 등장하는 때
- 조건 : 로컬 stdio 서버가 뒤에서 원격 SaaS API 를 부른다.
- 판단 : 클라이언트와 stdio 서버 사이 구간은 환경 자격증명을 쓴다. 서버와 SaaS 사이 구간은 그 SaaS 의 인증 방식을 쓴다(OAuth 일 수 있다). 두 구간을 섞어 "stdio 에도 OAuth 2.1 흐름" 이라 하면 틀린다.
- 등급 : (stdio 구간) · 업계 일반(업스트림 구간의 방식)
- 근거 : authorization
3. "사용자 권한 그대로 쓰고 싶다"
- 조건 : 사용자별 Salesforce 권한을 그대로 적용해야 한다. 그래서 받은 토큰을 넘기자는 제안이 나온다.
- 판단 : 뒤집히지 않는다. 업스트림 토큰은 업스트림 authorization server 가 따로 발급한다. 사용자 권한을 살리려면 업스트림 쪽 OAuth 에서 사용자 동의로 받는다(방식 세부는 규격 밖).
- 등급 : (금지) · 업계 일반(대안 방식)
- 근거 : authorization "The access token used at the upstream API is a separate token, issued by the upstream authorization server."
4. scope 를 크게 받는 게 나아 보이는 때
- 조건 : 동의창이 여러 번 떠서 사용자가 귀찮아한다.
- 판단 : 공식은 반대로 말한다. 과한 scope 목록이 오히려 동의 포기를 부른다. 토큰이 새면 피해도 크다. 최소로 받고 점진적으로 올린다. 거절된 scope 는 캐시해 반복 요청을 막는다.
- 근거 : security best practices
오답 재료
| 오답 | 왜 끌리나 | 왜 틀리나 | 근거 |
|---|---|---|---|
| "stdio 서버에도 OAuth 2.1 흐름" | 보안이면 무조건 OAuth 라고 여긴다 | stdio 는 SHOULD NOT 이다. 자격증명은 환경에서 받는다 | authorization |
| "원격 MCP 서버는 반드시(MUST) OAuth" | 원격이면 인증이 필수일 것 같다 | 인가 자체가 OPTIONAL 이다. HTTP 면 SHOULD 다 | authorization |
| "받은 사용자 토큰을 업스트림에 그대로 넘기면 권한이 유지돼 안전" | SSO 처럼 편하다 | 규격이 명시적으로 금지한다. 통제가 우회되고, 감사가 안 되고, 유출 통로가 된다 | security best practices |
| "audience 는 안 봐도 서명만 맞으면 됨" | JWT 서명 검증에 익숙하다 | 자기용 토큰인지 꼭 확인해야 한다 | authorization |
| "토큰을 URL 쿼리로 넘김" | 구현이 쉽다 | 금지다. 헤더로 보낸다 | authorization |
| "scope 는 처음에 전부 받는다" | 동의창이 한 번만 뜬다 | 피해 반경이 커지고 동의 포기를 부른다. 점진적으로 올린다 | security best practices |
| "Claude API 는 x-api-key 헤더만" | 옛 예제에 익숙하다 | 지금 기본은 Authorization: Bearer 다. x-api-key 는 레거시 대체 수단이다 | API overview |
참고
더 깊이 : 출처 · 상태 · 미해결 (참고)
출처
- 가이드 목표 3.2 공식 시험 가이드 v1.0
- MCP authorization 2025-11-25
- MCP security best practices
- mcp-connector
- API overview
- 릴리스 노트
상태
- 고침 1곳(API 키 헤더 — 목록은
x-api-key로만 적었음)
미해결
- 목록 3.2① 은 "Claude API 호출 자체는 API 키(x-api-key 헤더 — 근거 확인 필요)"였다. 지금 docs 는
Authorization: Bearer가 기본이고,x-api-key는 "Legacy fallback" 이다. 이 표기가 언제 바뀌었는지는 릴리스 노트에서 못 찾았다(검색어 "x-api-key", "Authorization header"). WIF 출시는 2026-05-04 다(릴리스 노트). - 규격 버전 : 이번 원문은 2025-11-25 버전이다. 시험이 이전 버전(2025-06-18) 기준이면 세부(client ID metadata 문서 등)가 다를 수 있다. 이 카드의 규칙(OPTIONAL · stdio 환경 · audience · passthrough 금지)이 이전 버전에도 있었는지는 확인하지 못했다. 이전 버전을 받지 않았다.
- 목록의 "'lethal trifecta'·'Confused Deputy' 용어 주의"는 원문과 맞다. confused deputy 절은 정적 client ID 프록시의 동의 문제다(authorization). passthrough 는 별도 항목이지만, 규격은 그것도 confused deputy 를 부를 수 있다고 적는다.