1. Background
OAuth 2.0 (RFC 6749) 은 Web Authorization Protocol (oauth) 워킹 그룹에서 산업 표준(industrial-standard)으로 개발된 Authorization Framework 입니다.
핵심 목적은 리소스 소유자의 자격증명을 클라이언트에 노출하지 않고, 범위(scope) 가 제한된 Access Token 을 발급해 리소스 접근을 위임 하는 것입니다.
이름이 "Authorization" 인 데서 알 수 있듯이 OAuth 2.0 은 인가(Authorization) 위임에 관한 표준이며, 로그인 같은 인증(Authentication) 은 다루지 않습니다. 두 개념의 구분은 §1.2 에서 정리하고, 인증이 필요할 때 사용하는 OpenID Connect (OIDC, OpenID Connect) 와의 관계도 같이 설명합니다.
1.1 OAuth 가 없던 시절의 문제
우리 애플리케이션 "App" 이 Google 의 메일을 가져와 자동으로 정리해주는 서비스라고 가정합니다. OAuth 가 없으면 "App" 은 사용자의 Google ID/PW 를 직접 받아 저장하고, 이를 이용해 메일을 수집해야 합니다.
이 구조에서 세 참여자 모두에게 문제가 있습니다.
- 사용자: "App" 이 자신의 Google ID/PW 를 안전하게 보관·사용할지 신뢰하기 어렵습니다. "App" 이 메일 외에 다른 영역(Drive, Calendar) 까지 마음대로 볼 수 있습니다.
- App: 모든 사용자의 원본 자격증명을 저장해야 하고, 유출 시 책임이 큽니다.
- Google: 외부 클라이언트의 보안 수준을 통제할 수 없고, 자격증명 유출 시 자사 계정 전체가 위험해집니다.
OAuth 는 사용자의 원본 자격증명을 클라이언트에 넘기지 않고, 권한 서버가 발급한 범위와 수명이 제한된 토큰 으로 리소스 서버에 접근하도록 해 이 문제를 해결합니다.
1.2 Authentication (인증) vs Authorization (인가)
OAuth 2.0 을 정확히 이해하려면 두 개념을 먼저 구분해야 합니다. 영문 약어로는 각각 AuthN / AuthZ 로 줄여 씁니다.
| 구분 | Authentication (AuthN, 인증) | Authorization (AuthZ, 인가) |
|---|---|---|
| 묻는 질문 | "너 누구야?" | "너 이거 해도 돼?" |
| 검증 대상 | 주체(principal) 의 신원(identity) | 주체에게 부여된 권한(permission) |
| 결과물 | 검증된 신원 정보 (user ID, 세션) | 허용/거부, 또는 권한 범위(scope) 가 담긴 토큰 |
| 흔한 수단 | 비밀번호, OTP, 생체인식, 패스키, 인증서 | ACL, RBAC/ABAC, 정책 엔진, OAuth Access Token |
| 회사 출입 비유 | 게이트에서 사원증·얼굴로 본인 확인 | 사원증의 등급에 따라 어느 층까지 들어갈 수 있는지 결정 |
순서는 일반적으로 인증 → 인가 입니다. "누구인지" 를 확인한 다음 "그 사람이 무엇을 할 수 있는지" 를 판단합니다. 다만 API Key 처럼 신원과 권한을 한 토큰에 묶어 인증 단계를 사실상 생략하는 경우도 있습니다.
OAuth 2.0 은 이 중 인가(AuthZ) 위임 프레임워크입니다. "Resource Owner 가 Client 에게 자기 리소스에 대한 접근 권한을 위임" 하는 절차와 토큰 발급 방식을 규정할 뿐, 사용자가 누구인지 확인하는 방법(인증) 자체는 정의하지 않습니다. 권한 서버 내부에서 사용자 인증이 일어나기는 하지만, 그 결과(신원) 를 클라이언트에 어떻게 전달할지는 OAuth 2.0 의 범위가 아닙니다.
이 공백을 메우기 위해 OpenID Connect (OIDC) 가 OAuth 2.0 위에 얇은 인증 레이어를 얹습니다. OIDC 는 Access Token 과 별개로 ID Token (JWT) 을 함께 발급해 "이 사용자는 누구다" 라는 신원 정보를 클라이언트에 전달합니다. 우리가 흔히 "소셜 로그인" 이라고 부르는 흐름(구글/카카오로 로그인) 은 사실상 OIDC 입니다.
Access Token 만 가지고 "로그인한 사용자" 를 식별하려고 하면 안 됩니다. Access Token 은 리소스 접근 권한 의 증거이지 사용자 신원 의 증거가 아닙니다. 사용자를 식별해야 한다면 OIDC ID Token 또는 별도 인증 메커니즘을 사용하십시오.
2. Introduction
2.1 Participants (Roles)
RFC 6749 는 네 가지 역할을 정의합니다.
- Resource Owner: 리소스의 주인. 보통 사용자입니다. (예: Gmail 계정 소유자)
- Client: 리소스에 접근하려는 애플리케이션. (예: Gmail 정리 서비스 "App")
- Resource Server: 보호된 리소스를 호스팅하는 서버. Access Token 을 검증하고 리소스를 반환합니다. (예: Gmail API)
- Authorization Server: Resource Owner 를 인증하고 동의를 받아 토큰을 발급합니다. (예: Google OAuth 2.0 endpoint)
Resource Server 와 Authorization Server 는 동일 사업자가 운영하지만 물리적으로는 다른 서버일 수 있습니다.
2.1.1 Client Type
RFC 6749 §2.1 은 클라이언트를 두 종류로 분류합니다. 어떤 Grant 와 보안 장치를 적용할지를 결정하는 기준입니다.
- Confidential Client:
client_secret을 안전하게 보관할 수 있는 클라이언트. 백엔드 서버 등. - Public Client: 자격증명을 안전하게 보관할 수 없는 클라이언트. SPA·모바일·데스크톱 앱 등. PKCE 가 필수입니다.
2.2 Process (Authorization Code Flow)
가장 일반적인 Authorization Code Grant 흐름입니다.
1. 사용자가 《 클라이언트 》 앱에서 로그인 버튼 클릭
│
│ (2. 권한 서버로 권한 요청: client_id, redirect_uri, scope, state, code_challenge)
∨
《 클라이언트 》 --------------------------------------> 《 권한 서버 》
│
│ (3. 사용자에게 로그인 페이지 리다이렉션)
∨
《 사용자 》 <------------------------------------------ 《 권한 서버 》
│
│ (4. 사용자 로그인 및 권한 동의)
∨
《 사용자 》 --------------------------------------------> 《 권한 서버 》
│
│ (5. '인가 코드' 발급 및 redirect_uri 로 리다이렉션) - 인가 코드는 브라우저를 경유해 클라이언트에 전달
∨
《 클라이언트 》 <---------------------------------------- 《 권한 서버 》
│
│ (6. 인가 코드 + client_secret(+ code_verifier) 로 토큰 교환 요청)
∨
《 클라이언트 》 --------------------------------------> 《 권한 서버 》
│
│ (7. Access Token (+ Refresh Token) 발급)
∨
《 클라이언트 》 <---------------------------------------- 《 권한 서버 》
│
│ (8. Access Token 으로 《 리소스 서버 》에 정보 요청)
∨
《 클라이언트 》 --------------------------------------> 《 리소스 서버 》
│
│ (9. 요청한 정보 반환)
∨
《 클라이언트 》 <---------------------------------------- 《 리소스 서버 》
권한 서버가 사용자(브라우저) 에게 Access Token 을 직접 주지 않고 인가 코드를 거치게 하는 이유 는 다음과 같습니다.
- 브라우저는 정보 노출 위험이 높은 환경(히스토리, 캐시, Referer, 확장 프로그램)입니다. Access Token 이 직접 노출되면 즉시 리소스 접근에 사용될 수 있습니다.
- 인가 코드는 일회용·단명 이며, 토큰 교환 단계에서 클라이언트 인증(
client_secret또는code_verifier) 이 추가로 요구되므로 코드만 탈취해서는 토큰을 받을 수 없습니다.
토큰 교환 단계에서는 인가 코드 외에 클라이언트 인증 이 함께 제출됩니다.
- Confidential Client:
client_id+client_secret(또는private_key_jwt,tls_client_auth등) - Public Client:
client_id+ PKCEcode_verifier(secret 없음)
클라이언트는 권한 서버에 사전 등록 되어 있어야 하며, 등록 시 client_id, client_secret(필요 시), redirect_uri, 허용 scope 등을 교환합니다.
2.3 Scope
Scope 는 클라이언트가 사용자 리소스의 어느 범위까지 접근할 수 있는지를 정의합니다. 사용자는 Scope 를 통해 "App" 이 Gmail 만 읽고 Drive 는 건드리지 못하도록 통제할 수 있습니다.
Scope 가 적용되는 흐름은 다음과 같습니다.
- 클라이언트 요청: 권한 요청 URL 에
scope=profile email처럼 필요한 권한 키워드를 포함합니다. - 사용자 동의: 권한 서버는 요청된 Scope 를 사용자에게 동의 화면으로 보여줍니다. (예: "프로필 정보와 이메일 주소 접근을 허용하시겠습니까?")
- 인가 코드 및 Access Token 발급: 사용자가 동의하면 권한 서버가 인가 코드를 발급하고, 클라이언트는 이를 교환해 해당 Scope 가 부여된 Access Token 을 받습니다.
- 권한 제한 적용: 리소스 서버는 Access Token 의 Scope 를 확인해 범위를 벗어난 요청을 거절합니다.
Scope 키워드의 일부는 OAuth/OIDC 표준으로 정의되지만(openid, profile, email 등), 대부분은 각 서비스 제공자가 자체 API 에 맞게 정의하고 개발자 문서에 공개합니다.
3. Grant Types
OAuth 2.0 은 클라이언트 종류와 사용 시나리오에 따라 여러 인가 방식(Grant Type) 을 정의합니다.
3.1 Authorization Code (+ PKCE)
2.2 절에서 설명한 흐름입니다. 현재 사실상 표준 으로 권장되는 방식입니다.
- 인가 코드를 먼저 받고, 백엔드(또는 PKCE) 로 Access Token 으로 교환합니다.
- Access Token 이 브라우저 URL/히스토리에 노출되지 않습니다.
- Confidential Client:
client_secret단독, 또는client_secret+ PKCE. - Public Client (SPA·모바일): PKCE 필수.
client_secret은 사용하지 않습니다.
RFC 9700 은 모든 클라이언트 에서 PKCE 를 사용할 것을 권고합니다(Confidential Client 도 포함).
3.2 Implicit
SPA 같은 브라우저 전용 환경을 위해 도입됐던 방식입니다.
- 인가 코드 단계 없이 권한 서버가 Access Token 을 URL Fragment(
#) 로 직접 반환합니다. - Access Token 이 브라우저 히스토리·Referer·서버 로그에 남을 수 있어 탈취 위험이 큽니다.
- Refresh Token 을 발급하지 않습니다.
- RFC 9700 은 Implicit 사용 회피를 권고합니다.
- 현재는 Authorization Code + PKCE 로 대체하십시오.
3.3 Resource Owner Password Credentials (ROPC)
사용자가 ID/PW 를 클라이언트에 직접 입력하고, 클라이언트가 이를 권한 서버에 그대로 전달해 Access Token 을 받는 방식입니다.
- 클라이언트가 사용자 자격증명을 직접 처리하므로 OAuth 의 핵심 목적(자격증명 위임) 을 훼손합니다.
- MFA·동의 화면·외부 IdP 와 결합되지 않습니다.
- RFC 9700: ROPC grant MUST NOT be used.
- 사용 금지. 1st-party 앱이 자체 로그인 UI 가 필요하더라도 Authorization Code + PKCE(필요 시 시스템 브라우저 우회) 또는 Device Authorization Grant 로 대체하십시오.
3.4 Client Credentials
사용자가 개입하지 않는 Machine-to-Machine (M2M) 통신에 사용합니다.
- 클라이언트가
client_id+client_secret(또는private_key_jwt) 으로 직접 권한 서버에 Access Token 을 요청합니다. - Resource Owner 가 없으므로 인가 코드·동의 화면·Refresh Token 이 모두 없습니다.
- 백엔드 서비스 간 API 호출, 배치 작업, 데몬 프로세스에 적합합니다.
3.5 그 외
- Refresh Token Grant (RFC 6749 §6): Refresh Token 으로 새 Access Token 을 받는 별도 Grant Type. (4.3 절 참고)
- Device Authorization Grant (RFC 8628): TV·CLI 처럼 입력 UI 가 제한된 기기를 위한 흐름. ROPC 대체 후보 중 하나.
4. Tokens
4.1 Access Token
리소스 서버에 접근할 때 사용하는 단기 자격증명입니다.
- HTTP 요청의
Authorization: Bearer <token>헤더로 전달합니다 (RFC 6750). - 유효 기간이 짧습니다 (일반적으로 수 분~1 시간).
- 토큰 포맷은 OAuth 2.0 표준이 강제하지 않습니다. 실무에서 흔한 두 가지 형태:
- JWT (Self-contained / Structured): 토큰 자체에 클레임이 들어있어 리소스 서버가 서명만 검증하면 됩니다. RFC 9068 (JWT Profile for OAuth 2.0 Access Tokens) 가 표준화한 형식입니다.
- Opaque (Reference Token): 의미 없는 무작위 문자열. 리소스 서버는 권한 서버의 Token Introspection (RFC 7662) 엔드포인트로 매번 검증합니다. 즉시 폐기가 가능한 대신 검증 비용이 더 듭니다.
4.2 Refresh Token
Access Token 만료 시 새 Access Token 을 받기 위한 장기 자격증명입니다.
- 권한 서버의 토큰 엔드포인트에만 보내며, 리소스 서버에는 전달하지 않습니다.
- 수명이 길어(수일~수개월) 탈취 시 파급력이 큽니다. HttpOnly·Secure 쿠키, 서버 세션, OS 키체인 등 안전한 저장소에 보관해야 합니다.
- 발급 여부는 authorization server 정책, 요청한 scope, client type 에 따라 결정됩니다 (RFC 6749, optional). Resource Owner 가 없는 Client Credentials 나 Access Token 만 반환하는 Implicit 흐름에서는 발급하지 않는 것이 일반적입니다.
- 탈취 대비를 위해 Refresh Token Rotation 적용을 강력히 권고합니다 (6.4 절).
4.3 Token 갱신 흐름
《 클라이언트 》 ---[Access Token 만료 감지]---> 《 권한 서버 》
grant_type=refresh_token
refresh_token=<token>
client_id (+ client_secret | code_verifier)
│
∨
《 클라이언트 》 <---[새 Access Token (+ Rotated Refresh Token)]---
4.4 Token Revocation / Introspection
- Revocation (RFC 7009): 클라이언트가 Refresh/Access Token 을 능동적으로 폐기. 로그아웃·기기 제거 시 사용.
- Introspection (RFC 7662): Opaque token 의 유효성·메타데이터(scope, client_id, exp 등) 를 권한 서버에 질의.
5. PKCE (Proof Key for Code Exchange)
PKCE (RFC 7636) 는 Authorization Code Grant 에서 인가 코드 탈취·교환 공격(authorization code interception) 을 막기 위한 확장입니다. Public Client(SPA·모바일) 에서는 필수이며, RFC 9700 은 Confidential Client 에도 적용을 권고합니다.
5.1 동작 원리
1. 클라이언트가 충분히 무작위한 code_verifier 생성 (43~128 자, [A-Z][a-z][0-9]-._~)
2. code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) (S256 method)
3. 인가 요청에 code_challenge 와 code_challenge_method=S256 포함
4. 권한 서버는 code_challenge 를 인가 코드와 함께 저장하고 코드 발급
5. 클라이언트가 토큰 교환 시 인가 코드 + 원본 code_verifier 전송
6. 권한 서버: BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == 저장된 code_challenge 검증 → 토큰 발급
plain method (해시 없이 비교) 도 스펙상 존재하지만 보안상 사용하지 않습니다. 항상 S256 사용.
5.2 보안 효과
공격자가 redirect 단계에서 인가 코드를 탈취해도 원본 code_verifier 를 알 수 없으므로 토큰 교환을 할 수 없습니다. Client Secret 없이도 코드 교환의 무결성을 보장하므로 Public Client 에 특히 중요합니다.
6. 보안 필수 항목
6.1 state 파라미터 (CSRF 방어)
인가 요청 시 클라이언트가 생성한 무작위 값을 state 로 전달하고, 콜백 시 동일한 값이 돌아왔는지 검증합니다.
1. 클라이언트 → 권한 서버: ?response_type=code&state=<random>
2. 권한 서버 → 클라이언트: ?code=<code>&state=<random>
3. 클라이언트: 받은 state == 저장한 state 비교 → 불일치 시 즉시 거부
state 검증이 없으면 공격자가 자신의 인가 코드를 피해자 세션에 주입해 피해자 계정에 공격자 외부 계정을 연결시키는 CSRF 가 가능합니다. PKCE 가 있어도 state 는 별도로 필요합니다 (역할이 다름).
6.2 redirect_uri Exact Match 검증
권한 서버는 등록된 redirect_uri 와 인가 요청의 redirect_uri 를 완전 일치(exact string match) 로 비교해야 합니다. 접두사 일치·와일드카드·서브도메인 허용은 오픈 리다이렉트 와 인가 코드 탈취로 이어집니다.
클라이언트도 받은 redirect 가 자신의 도메인인지 다시 한 번 확인하는 것이 안전합니다.
6.3 Authorization Code 일회용 사용
인가 코드는 토큰 교환에 단 한 번 사용된 후 즉시 무효화되어야 합니다 (RFC 6749 §10.5, §4.1.2). 권한 서버가 동일 코드의 두 번째 교환을 감지하면 해당 코드로 이미 발급한 토큰 전체를 폐기 하는 것이 권고됩니다 (코드 재사용 = 탈취 신호).
또한 인가 코드는 수명을 짧게(보통 10 분 이내) 두어야 합니다.
6.4 Refresh Token Rotation + Reuse Detection
Refresh Token 을 사용할 때마다 새 Refresh Token 을 발급하고 이전 토큰을 즉시 무효화합니다. 무효화된 토큰의 재사용이 관측되면 탈취 신호 로 간주해 해당 클라이언트/사용자의 Refresh Token 계열 전체를 폐기합니다 (Automatic Reuse Detection).
1. 클라이언트: refresh_token=RT1 → 권한 서버
2. 권한 서버: 새 AT2 + 새 RT2 발급, RT1 무효화
3. 공격자가 RT1 재사용 시도 → 권한 서버: RT1 이미 사용됨 감지
→ 해당 token family 전체(AT2, RT2 포함) 폐기 → 사용자 재로그인 필요
Public Client 에서 Refresh Token 을 사용한다면 Rotation 은 사실상 필수입니다.
6.5 그 외 권고 사항 (요약)
- HTTPS 전 구간 강제: 인가 요청·콜백·토큰 엔드포인트 모두 TLS.
- Audience 검증 (
aud클레임): JWT Access Token 은 의도된 리소스 서버가 자신을 대상으로 한 토큰인지 확인해야 함. 그렇지 않으면 다른 리소스 서버용 토큰을 받아 처리하는 confused deputy 발생 가능. - 최소 권한 Scope: 클라이언트가 필요한 최소 Scope 만 요청.
- 토큰 저장 위치: 브라우저에서
localStorage는 XSS 시 즉시 탈취됨. HttpOnly 쿠키 또는 메모리(BFF 패턴) 권장. - Sender-Constrained Token (DPoP RFC 9449, mTLS RFC 8705): Access Token 을 특정 키 보유자에게만 사용 가능하도록 묶는 강화 옵션. 고보안 환경에서 검토.
7. 관련 RFC
| RFC | 제목 |
|---|---|
| RFC 6749 | OAuth 2.0 Authorization Framework (Core) |
| RFC 6750 | Bearer Token Usage |
| RFC 7009 | Token Revocation |
| RFC 7636 | PKCE |
| RFC 7662 | Token Introspection |
| RFC 8628 | Device Authorization Grant |
| RFC 8705 | mTLS Client Authentication / Certificate-Bound Tokens |
| RFC 9068 | JWT Profile for OAuth 2.0 Access Tokens |
| RFC 9449 | DPoP (Demonstrating Proof of Possession) |
| RFC 9700 | OAuth 2.0 Security Best Current Practice |
Document History
- 2026-05-27T11:09:11+09:00 - Written by claude-sonnet-4-6
- 2026-05-27T11:09:11+09:00 - Edited by claude-sonnet-4-6 (정확성 보강: Authorization/Authentication 구분, client_secret 수정, Implicit/ROPC RFC 9700 반영, PKCE 수식 수정, 보안 필수 항목 추가)
- 2026-05-27T13:13:53+09:00 - Edited by claude-opus-4-7 (검증·정정: "인증 위탁" 표현 제거, Scope 절 오타·"인가 토큰" 용어 정정, Client Type/PKCE 모든 클라이언트 권고로 수정, Access Token 포맷에 Opaque/JWT 구분, Refresh Token Rotation·redirect_uri·인가 코드 일회용 상세화, Token Introspection/Revocation·Device Grant·DPoP/mTLS 언급 추가, 관련 RFC 표 추가, 헤딩 번호 형식 통일)
- 2026-05-28T09:45:35+09:00 - Edited by claude-opus-4-7 (§1.2 Authentication vs Authorization 절 신설: AuthN/AuthZ 정의·비교 표·OIDC ID Token 과의 관계 정리)