OAuth 2.0 쉽게 이해하기
내 서비스에서 구글 캘린더에 있는 사용자의 일정을 가져와야 한다면 어떻게 해야 할까요? 사용자에게 구글 이메일과 비밀번호를 알려달라고 할 수도 없고, 직접 구글 캘린더에서 일정을 내려받아 달라고 할 수도 없겠죠 😅
OAuth 2.0은 바로 이런 문제를 해결하기 위해 만들어진 프로토콜입니다. 사용자의 비밀번호를 직접 받지 않고도, 사용자가 명시적으로 동의한 범위 내에서 데이터에 접근할 수 있는 표준화된 방법을 제공하는데요. 이번 글에서는 OAuth 2.0의 기본 개념부터 Authorization Code 인가 흐름까지 차근차근 살펴보겠습니다.
사용자 입장에서의 OAuth 2.0
웹 서비스를 이용하다 보면 아래와 같이 “이 앱이 내 구글 계정에 접근하려고 합니다”라는 화면을 자주 만나게 됩니다.

평소에는 별 생각 없이 “허용”을 클릭하고 넘어가지만, 이 화면이 사실 OAuth 2.0의 핵심이에요. 해당 서비스가 구글에 저장된 나의 데이터에 대한 접근 권한을 요청하는 것이고, 사용자는 요청된 권한의 범위를 확인하고 허용할지 거부할지 선택할 수 있습니다.
이처럼 우리는 일상에서 이미 OAuth 2.0을 통해 다른 서비스가 구글이나 카카오에 저장된 나의 데이터를 사용하도록 허락해주고 있습니다.
서비스 입장에서의 OAuth 2.0
이번에는 서비스 개발자 입장에서 생각해볼까요? 구글 캘린더와 연동되는 일정 관리 앱을 만든다고 가정해봅시다.
가장 단순한 방법은 사용자에게 직접 구글 캘린더에 들어가서 일정을 내려받아 우리 서비스에 올리라고 하는 것입니다. 번거롭기도 하거니와, 시시각각 바뀌는 사용자의 일정을 어떻게 실시간으로 반영할지도 막막합니다. 대부분의 사용자는 이런 과정이 필요하다면 아예 서비스를 쓰려고 하지도 않겠죠.
그러면 사용자에게 구글 계정의 이메일과 비밀번호를 알려달라고 하면 어떨까요? 기술적으로 구글 캘린더뿐 아니라 Gmail이나 구글 드라이브에 있는 민감한 개인 정보까지 모두 접근할 수 있게 되어 보안상 매우 위험합니다. 설사 사용자가 순순히 구글 계정 정보를 넘겨준다고 해도, 서비스 입장에서 모든 사용자의 구글 로그인 정보를 안전하게 관리해야 하는 부담이 생깁니다.
이렇게 다른 서비스에 저장된 사용자 데이터를 안전하게 가져오는 것은 생각보다 쉽지 않은 일인데요. OAuth 2.0은 이 딜레마를 깔끔하게 풀어줍니다. 사용자의 비밀번호 없이도, 사용자의 명시적 동의 하에 필요한 데이터에만 접근할 수 있게 해주기 때문입니다. 구글과 페이스북을 시작으로 수많은 서비스가 OAuth 2.0을 채택하면서, 사실상 인가(authorization) 프로토콜의 표준이 되었습니다.
OAuth 2.0 용어
OAuth 2.0을 이해하려면 먼저 등장하는 용어부터 정리해두는 것이 좋습니다. 등장인물 네 명과 핵심 개념 두 가지만 알면 전체 흐름을 따라갈 수 있어요.
Resource Owner는 데이터의 주인, 즉 사용자입니다. 구글 캘린더 예시에서 자신의 일정 데이터를 소유하고 있는 사람이죠.
Client는 사용자의 데이터에 접근하려는 애플리케이션입니다. 위 예시에서 우리가 만들고 있는 일정 관리 앱이 여기에 해당하는데요. “클라이언트”라는 이름이 좀 헷갈릴 수 있지만, 구글 입장에서 우리 앱이 고객(client)이라서 이렇게 부릅니다.
Authorization Server는 사용자의 인가를 처리하는 서버입니다. 로그인 화면을 보여주고, 권한 허용 여부를 물어본 다음, 인가가 완료되면 access token을 발급하는 역할을 합니다.
Resource Server는 사용자의 데이터를 실제로 보관하고 있는 서버입니다. 구글 캘린더 API 서버가 이에 해당하며, access token을 검증한 뒤 데이터를 응답합니다. Authorization Server와 Resource Server는 같은 회사(예: 구글)에서 운영하지만 역할이 다릅니다.
여기에 Scope라는 개념이 있습니다. 접근 권한의 범위를 나타내는 것으로, “캘린더 읽기”, “연락처 수정” 같은 세분화된 권한 단위입니다. 사용자는 요청된 scope를 보고 어떤 데이터에 어떤 작업을 허용하는지 판단하게 됩니다.
마지막으로 Access Token은 인가가 완료된 후 발급되는 일종의 출입증입니다. 클라이언트가 Resource Server에 API를 호출할 때 이 토큰을 함께 전달하면, 서버가 토큰을 검증하여 요청을 처리합니다.
Authorization Code 인가 흐름
OAuth 2.0에는 여러 인가 방식이 있는데, 그중에서 가장 널리 쓰이고 보안성도 높은 것이 Authorization Code 방식입니다. 웹 서버가 있는 애플리케이션에서 주로 사용하며, 전체 흐름은 다섯 단계로 나뉩니다.
- 클라이언트가 사용자를 Authorization Server의 로그인 화면으로 리다이렉트시킵니다.
- 사용자가 로그인하고, 요청된 권한을 확인한 뒤 허용합니다.
- Authorization Server가 사용자를 클라이언트의 redirect URI로 돌려보내면서 authorization code를 전달합니다.
- 클라이언트가 이 authorization code를 Authorization Server에 보내서 access token으로 교환합니다.
- 클라이언트가 access token으로 Resource Server에 API를 호출합니다.
여기서 3단계에서 바로 access token을 주지 않고 authorization code라는 중간 단계를 두는 이유가 뭘까요? 3단계까지는 사용자의 브라우저를 통해 데이터가 전달됩니다(front channel). 브라우저 주소창이나 히스토리에 access token이 노출되면 보안상 위험하겠죠. 반면 4단계에서 authorization code를 access token으로 교환하는 과정은 클라이언트의 백엔드 서버에서 Authorization Server로 직접 요청을 보내기 때문에(back channel) 토큰이 사용자 브라우저에 노출되지 않습니다.
클라이언트 등록
Authorization Code 흐름을 시작하기 전에 먼저 클라이언트를 Authorization Server에 등록해야 합니다. 구글의 경우 Google API Console에서 프로젝트를 생성하고 OAuth 클라이언트 ID를 발급받으면 됩니다. 이때 세 가지 중요한 정보를 얻거나 설정하게 되는데요.
Client ID는 클라이언트 애플리케이션을 식별하는 공개 식별자입니다. 인가 요청 URL에 포함되어 Authorization Server가 어떤 앱의 요청인지 파악하는 데 쓰입니다.
Client Secret은 클라이언트의 비밀 키입니다. authorization code를 access token으로 교환할 때 Client ID와 함께 전달하여 클라이언트의 신원을 증명하는데요. 이름 그대로 절대 외부에 노출되면 안 되는 값이므로 반드시 서버 측에서만 사용해야 합니다.
Redirect URI는 사용자가 권한을 허용한 뒤 되돌아올 URL입니다. Authorization Server는 여기에 등록된 URI로만 리다이렉트를 허용하므로, 공격자가 redirect URI를 변조해서 authorization code를 가로채는 것을 방지할 수 있습니다.
authorization code 획득
클라이언트 등록이 끝났다면 인가 흐름을 시작할 수 있습니다. 사용자를 Authorization Server의 인가 엔드포인트로 리다이렉트시키면서, 필요한 정보를 쿼리 파라미터로 전달합니다.
https://authorization-server.com/authorize
?response_type=code
&client_id=my_client_id
&redirect_uri=https://my-app.com/callback
&scope=calendar:read contacts:read
&state=random_csrf_token
response_type=code는 Authorization Code 방식을 사용하겠다는 의미이고, scope은 요청하는 권한의 범위입니다.
state 파라미터는 CSRF(Cross-Site Request Forgery) 공격을 방지하기 위한 무작위 문자열인데요.
클라이언트가 생성해서 보내고 콜백에서 동일한 값이 돌아오는지 검증합니다.
사용자가 로그인하고 권한을 허용하면, Authorization Server는 redirect URI로 사용자를 돌려보내면서 authorization code를 쿼리 파라미터로 전달합니다.
https://my-app.com/callback
?code=SplxlOBeZQQYbYS6WxSbIA
&state=random_csrf_token
이 authorization code는 일회용이고 보통 수 분 내에 만료됩니다. 받자마자 다음 단계에서 access token으로 교환해야 해요.
access token 발급
authorization code를 받았다면 클라이언트의 백엔드 서버에서 Authorization Server의 토큰 엔드포인트로 POST 요청을 보내 access token을 발급받습니다.
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://my-app.com/callback
&client_id=my_client_id
&client_secret=my_client_secret
이 요청은 서버 간 직접 통신(back channel)으로 이뤄지기 때문에 client_secret을 안전하게 전달할 수 있습니다.
응답으로는 다음과 같은 JSON을 받게 됩니다.
{
"access_token": "ya29.a0ARrdaM...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//0eXy7...",
"scope": "calendar:read contacts:read"
}
access_token은 API 호출에 사용할 토큰이고, expires_in은 토큰의 유효 기간(초)입니다.
refresh_token은 access token이 만료된 후 새 토큰을 발급받을 때 사용하는데, 뒤에서 더 자세히 살펴보겠습니다.
API 호출
access token을 확보했다면 Resource Server에 API를 호출하는 것은 간단합니다.
HTTP 요청의 Authorization 헤더에 Bearer 키워드와 함께 access token을 전달하면 됩니다.
GET /v1/calendar/events HTTP/1.1
Host: api.example.com
Authorization: Bearer ya29.a0ARrdaM...
Resource Server는 전달받은 access token을 검증하고, 토큰에 부여된 scope에 해당하는 데이터만 응답합니다.
토큰이 만료되었거나 scope에 맞지 않는 요청이면 401 Unauthorized 또는 403 Forbidden 응답을 반환하죠.
access token이 JWT(JSON Web Token) 형식이라면 서버에서 자체적으로 서명을 검증할 수 있어서, Authorization Server에 매번 토큰 유효성을 확인할 필요가 없습니다. 반면 불투명(opaque) 토큰은 Resource Server가 Authorization Server에 토큰 유효성을 별도로 확인해야 합니다.
access token 갱신
access token에는 유효 기간이 있어서 보통 1시간 정도면 만료됩니다. 매번 만료될 때마다 사용자에게 다시 로그인하고 권한을 허용해달라고 하면 불편하겠죠?
이 문제를 해결하는 것이 바로 refresh token입니다. access token을 발급받을 때 함께 받은 refresh token을 Authorization Server의 토큰 엔드포인트에 보내면 새 access token을 발급받을 수 있습니다.
POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=1//0eXy7...
&client_id=my_client_id
&client_secret=my_client_secret
refresh token은 access token보다 유효 기간이 훨씬 길고, 사용자가 명시적으로 권한을 철회하지 않는 한 계속 사용할 수 있는 경우도 있습니다. 그래서 refresh token이 유출되면 access token보다 피해가 크기 때문에, 반드시 안전한 서버 환경에서만 보관해야 합니다.
최근 보안 권고에서는 refresh token을 한 번 쓰고 폐기하는 rotation 방식이 표준으로 자리 잡고 있는데요. 새 access token을 발급할 때마다 refresh token도 새 값으로 교체하고, 기존 값은 즉시 무효화하는 방식입니다. 공개 클라이언트(모바일 앱, SPA 등)에서는 OAuth 2.1 스펙에 따라 refresh token rotation이 필수로 요구됩니다.
지금까지는 각 엔드포인트가 “무엇을 하는지”를 중심으로 살펴봤는데요. 엔드포인트별 전체 파라미터와 에러 코드, 클라이언트 인증 방식의 차이까지 파고드는 내용은 OAuth 2.0 엔드포인트 제대로 이해하기에 따로 정리했습니다. 또한 엔드포인트 URL을 코드에 박아 넣지 않고 런타임에 자동으로 발견하는 최신 방식은 OAuth 2.0 메타데이터와 엔드포인트 동적 발견에서 다루고 있습니다.
다른 인가 방식
Authorization Code 외에도 OAuth 2.0은 애플리케이션 유형에 따라 여러 인가 방식을 제공합니다.
Implicit 방식은 authorization code 교환 과정 없이 access token을 redirect URI를 통해 바로 전달합니다. 브라우저 기반 SPA에서 쓰기 간편하다는 이유로 한때 널리 쓰였지만, 토큰이 URL에 노출되는 보안 취약점 때문에 OAuth 2.1 드래프트에서 공식적으로 제거되었습니다. 지금 새로운 애플리케이션을 만든다면 Implicit 대신 Authorization Code + PKCE를 쓰는 것이 권장됩니다. 구글 API 호출 실습은 OAuth 2.0으로 구글 API 호출하기에서 다루고 있으니 참고해보세요.
Client Credentials 방식은 사용자 없이 클라이언트가 자신의 Client ID와 Secret만으로 access token을 발급받습니다. 서버 간 통신(machine-to-machine)에서 주로 사용되며, 사용자 데이터가 아닌 서비스 자체의 리소스에 접근할 때 적합합니다.
Authorization Code + PKCE 방식은 Authorization Code 흐름에 PKCE(Proof Key for Code Exchange)라는 보안 장치를 추가한 것입니다.
Client Secret을 안전하게 보관할 수 없는 모바일 앱이나 SPA에서 authorization code 탈취 공격을 방지하려고 만들어졌는데요.
요청마다 클라이언트가 동적으로 생성하는 code_verifier와 code_challenge를 통해 인가 요청의 발신자를 검증합니다.
원래는 공개 클라이언트를 위한 보완책이었지만 OAuth 2.1에서는 모든 OAuth 클라이언트에 PKCE가 필수로 요구됩니다.
PKCE의 동작 원리와 구현 방법이 궁금하다면 PKCE로 Authorization Code 흐름 안전하게 보호하기에서 자세히 다루고 있습니다.
OAuth 2.1로의 이동
지금까지 살펴본 내용은 OAuth 2.0(RFC 6749)을 기준으로 한 것인데요. IETF는 2020년부터 OAuth 2.1이라는 통합 드래프트를 진행하고 있습니다. 완전히 새로운 프로토콜이 아니라, 지난 10년간 쌓인 보안 모범 사례를 OAuth 2.0 위에 정리한 “정돈된 버전”이라고 보시면 됩니다.
주요 변경 사항을 요약하면 이렇습니다. 우선 PKCE가 모든 OAuth 클라이언트에 필수가 되었고, 보안 취약점이 많은 Implicit 방식과 Resource Owner Password Credentials 방식은 제거되었습니다. 또한 공개 클라이언트의 refresh token rotation이 의무화되었고, redirect URI는 정확히 일치(exact match) 방식으로 검증하도록 강화되었습니다.
OAuth 2.1을 실제로 활용하는 최근 사례가 궁금하다면 MCP Authentication을 참고해보세요. Model Context Protocol은 OAuth 2.1을 기반으로 RFC 9728과 RFC 8707을 조합해 AI 에이전트 시대에 맞는 인증 스펙을 정의하고 있습니다.
OAuth 2.0과 소셜 로그인
한 가지 짚고 넘어갈 점이 있습니다. OAuth 2.0은 인가(authorization) 프로토콜이지 인증(authentication) 프로토콜이 아닙니다. “이 사용자가 누구인지” 알려주는 것이 아니라, “이 사용자의 데이터에 접근해도 되는지” 허락을 받는 것이 목적이에요.
그런데 많은 서비스에서 “구글로 로그인”, “카카오로 로그인” 같은 소셜 로그인을 OAuth 2.0 기반으로 구현하고 있죠. 이게 가능한 건 OAuth 2.0 위에 인증 기능을 얹은 OpenID Connect(OIDC) 프로토콜 덕분입니다. OIDC는 OAuth 2.0의 인가 흐름을 그대로 따르되, access token과 함께 사용자 정보가 담긴 ID Token을 추가로 발급합니다.
소셜 로그인 구현이 궁금하다면 구글 OpenID Connect 사용법을 참고해보세요.
마치며
OAuth 2.0의 핵심은 사용자의 비밀번호를 직접 다루지 않으면서도 필요한 데이터에 접근할 수 있도록 해주는 것입니다. Authorization Code 흐름에서 front channel과 back channel을 분리하여 보안을 확보하는 설계를 이해하면, 대부분의 OAuth 2.0 연동 작업을 무리 없이 수행할 수 있을 거예요. 새 프로젝트를 시작한다면 OAuth 2.1 권고대로 Authorization Code + PKCE를 기본으로 쓰고, refresh token rotation과 정확한 redirect URI 검증까지 챙기는 것이 좋습니다.
이 글에서는 프로토콜의 개념과 흐름을 위주로 살펴보았는데, 실제 구현이 궁금하다면 OAuth 2.0으로 구글 API 호출하기를 읽어보시면 좋겠습니다. access token의 내부 구조가 궁금하다면 JWT(JSON Web Token)도 함께 참고해보세요. 쿠키와 세션을 통한 전통적인 인증 방식과 비교해보는 것도 OAuth 2.0의 가치를 이해하는 데 도움이 될 거예요. OAuth 서버를 직접 구축하는 대신 관리형 서비스에 맡기고 싶다면 Auth0나 WorkOS 포스팅도 참고해보세요.
더 자세한 내용은 The OAuth 2.0 Authorization Framework (RFC 6749)과 OAuth 2.0 - oauth.net를 참고하세요.
This work is licensed under
CC BY 4.0