OAuth 2.0 설명: Authorization Codes에서 PKCE까지 (전체 그림)

발행: (2026년 3월 28일 PM 03:28 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

OAuth는 어디에나 존재하고 대부분의 개발자는 내부에서 무슨 일이 일어나고 있는지 제대로 이해하지 못한 채 사용합니다. “Google로 로그인”을 클릭하면 마법처럼 동작하고 로그인됩니다. 하지만 무언가가 깨지면—토큰이 만료되거나, 리디렉션이 실패하거나, 스코프가 잘못되면—당신은 한 번도 배워보지 않은 프로토콜을 디버깅하게 됩니다.

네 가지 역할

OAuth에는 네 명의 주체가 있으며, 이들을 혼동하는 것이 대부분의 혼란의 시작입니다:

  • Resource Owner — 바로 여러분, 사용자입니다. 여러분이 데이터를 소유합니다.
  • Client — 여러분의 데이터에 접근을 요청하는 애플리케이션입니다. 웹 앱, 모바일 앱, 혹은 CLI 도구일 수 있습니다.
  • Authorization Server — 여러분이 권한을 부여하면 토큰을 발급합니다. Google, GitHub, Auth0 등이 인증 서버를 운영합니다.
  • Resource Server — 실제 데이터를 보관하고 있는 API입니다. 경우에 따라 인증 서버와 같은 회사가 운영하기도 하고, 별개일 수도 있습니다.

OAuth의 핵심 목적: Client비밀번호를 전혀 보지 않은 채 Resource Server에 제한된 접근 권한을 얻는 것입니다.

Authorization Code 흐름 (단계별)

이것은 가장 일반적인 OAuth 흐름이며, 거의 모든 경우에 사용해야 하는 흐름입니다:

  1. 앱이 사용자를 인증 서버로 리디렉션합니다.
    URL에는 클라이언트 ID, 요청된 스코프(앱이 원하는 권한), 리디렉션 URI(돌아올 위치), 그리고 state 파라미터(CSRF 방지)가 포함됩니다.

  2. 사용자가 로그인하고 동의합니다.
    인증 서버는 앱이 요청하는 권한을 보여줍니다 — “Acme App이 귀하의 이메일과 프로필을 읽고 싶어합니다.” 사용자는 예 또는 아니오를 선택합니다.

  3. 인증 서버가 코드와 함께 리디렉션합니다.
    토큰이 아니라 짧은 수명의 인증 코드가 반환됩니다. 이 코드는 단독으로는 사용할 수 없습니다.

  4. 앱이 코드를 토큰으로 교환합니다.
    이는 서버‑대‑서버(백엔드에서 인증 서버로) 방식으로 이루어지며, 인증 코드와 클라이언트 시크릿을 사용합니다. 응답에는 액세스 토큰과 보통 리프레시 토큰이 포함됩니다.

  5. 앱이 액세스 토큰을 사용해 API를 호출합니다.
    토큰은 Authorization 헤더에 넣어 전송됩니다. 리소스 서버는 토큰을 검증하고 데이터를 반환합니다.

왜 3단계에서 바로 토큰을 반환하지 않을까요? 브라우저에서 리디렉션이 이루어지기 때문에 URL 주소창, 브라우저 기록, 서버 로그 등에 토큰이 노출될 수 있습니다. 코드‑대‑토큰 교환은 서버 측에서 이루어지므로 실제로 안전합니다.

PKCE: 공용 클라이언트를 위한 격차 해소

인증 코드 흐름에는 약점이 있습니다: 클라이언트가 비밀을 보관할 수 없는 경우는 어떨까요? 모바일 앱과 싱글‑페이지 앱은 전체 소스 코드를 사용자에게 전달하므로, 클라이언트 비밀을 안전하게 저장할 장소가 없습니다.

PKCE(“픽시”라고 발음) — Proof Key for Code Exchange — 가 이를 해결합니다. 작동 방식은 다음과 같습니다:

  1. 인증 요청 전에, 클라이언트는 code_verifier 라는 무작위 문자열을 생성하고, 그 문자열의 SHA‑256 해시인 code_challenge 를 계산합니다.

  2. 인증 요청 중에, 클라이언트는 code_challenge 를 인증 서버에 보냅니다.

  3. 토큰 교환 시에, 클라이언트는 원본 code_verifier 를 보냅니다.
    인증 서버는 이를 해시하여 이전에 받은 챌린지와 일치하는지 확인합니다.

인증 코드를 가로채는 공격자는 원본 code_verifier 가 없기 때문에 이를 사용할 수 없습니다. 수학적으로는 일방향이며, 검증자에서 챌린지로는 변환할 수 있지만 그 반대는 불가능합니다.

OAuth 2.1은 모든 클라이언트, 특히 공용 클라이언트에 대해 PKCE를 필수로 지정했습니다. 이는 보안 커뮤니티가 이 기술을 얼마나 중요하게 여기는지를 보여줍니다.

Access Tokens vs Refresh Tokens

  • Access tokens는 짧은 수명(몇 분에서 몇 시간)이며 API 호출에 사용됩니다.
  • Refresh tokens는 장기간 유효하고 강력합니다—하나를 잃으면 세션을 잃은 것과 같습니다.

Best practice: Refresh token을 매 사용 시마다 교체(일회용 토큰)하고 안전하게 저장합니다.

OAuth vs OpenID Connect

  • OAuth handles authorization — “이 앱이 내 사진에 접근할 수 있나요?”
  • OpenID Connect (OIDC) adds authentication on top — “이 사용자는 누구인가요?”

OIDC는 ID token을 도입하는데, 이 토큰은 JWT 형식으로 사용자 신원 클레임(이름, 이메일 등)을 포함합니다.

  • If you need “Sign in with X” — that’s OIDC.
  • If you need “let this app read my calendar” — that’s OAuth.

실제로 대부분의 구현에서는 두 가지를 함께 사용합니다.

내가 본 흔한 실수들

  • localStorage에 토큰을 저장. 페이지의 모든 JavaScript에서 접근 가능하며 XSS 공격에 노출됩니다. 대신 HttpOnly 쿠키나 메모리 내 저장소를 사용하세요.
  • 범위가 지나치게 넓음. 필요한 최소 권한만 요청하세요. 이메일을 읽기만 하면 쓰기 권한을 요청하지 마세요.
  • state 파라미터를 검증하지 않음. 이는 CSRF 방어 수단입니다. 이를 생략하면 공격자가 사용자를 속여 자신의 OAuth 세션에 계정을 연결하도록 만들 수 있습니다.
  • 액세스 토큰을 신원 증명으로 사용. 액세스 토큰은 “이 요청은 인증됨”을 의미하지만, 누가 요청했는지는 신뢰할 수 있게 알려주지 않습니다. 신원 확인을 위해 OIDC ID 토큰을 사용하세요.

결론

OAuth는 공유된 자격 증명 없이 위임된 접근을, 신뢰할 수 없는 네트워크를 통해, 다양한 클라이언트 유형에 대해 해결하는 정말 어려운 문제를 다루기 때문에 복잡해 보입니다. 각 단계 뒤에 있는 이유—코드 교환이 왜 필요한지, PKCE가 왜 존재하는지, 토큰이 접근 토큰과 갱신 토큰으로 나뉘는 이유—를 이해하면 프로토콜이 훨씬 명확해집니다.

위의 영상은 각 단계를 시각적으로 설명합니다. OAuth를 처음부터 구현하고 있다면, 공식 RFC 6749와 Auth0 문서를 함께 보면서 시청하는 것을 권장합니다. 이론과 구현 예제를 함께 공부하는 것이 이 내용을 빠르게 이해하는 가장 좋은 방법입니다.

지금 OAuth로 무언가를 만들고 있나요? 댓글로 알려 주세요 — 디버그를 도와드릴게요.

0 조회
Back to Blog

관련 글

더 보기 »