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 + PKCE code_verifier (secret 없음)

클라이언트는 권한 서버에 사전 등록 되어 있어야 하며, 등록 시 client_id, client_secret(필요 시), redirect_uri, 허용 scope 등을 교환합니다.

2.3 Scope

Scope 는 클라이언트가 사용자 리소스의 어느 범위까지 접근할 수 있는지를 정의합니다. 사용자는 Scope 를 통해 "App" 이 Gmail 만 읽고 Drive 는 건드리지 못하도록 통제할 수 있습니다.

Scope 가 적용되는 흐름은 다음과 같습니다.

  1. 클라이언트 요청: 권한 요청 URL 에 scope=profile email 처럼 필요한 권한 키워드를 포함합니다.
  2. 사용자 동의: 권한 서버는 요청된 Scope 를 사용자에게 동의 화면으로 보여줍니다. (예: "프로필 정보와 이메일 주소 접근을 허용하시겠습니까?")
  3. 인가 코드 및 Access Token 발급: 사용자가 동의하면 권한 서버가 인가 코드를 발급하고, 클라이언트는 이를 교환해 해당 Scope 가 부여된 Access Token 을 받습니다.
  4. 권한 제한 적용: 리소스 서버는 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 과의 관계 정리)
🔒 Admin 로그인