콘텐츠 대표 이미지 - OAuth 2.0 완전 정복: 인증 코드부터 암시적 흐름까지, 내 데이터는 내가 지킨다!

OAuth 2.0 완전 정복: 인증 코드부터 암시적 흐름까지, 내 데이터는 내가 지킨다!

안녕! 오늘은 웹 개발자라면 무조건 알아야 할, 하지만 처음엔 머리 좀 아픈 'OAuth 2.0'에 대해 제대로 파헤쳐 볼 거야.
소셜 로그인부터 API 연동까지, 현대 웹의 필수 관문인 OAuth 2.0의 다양한 인증 흐름을 친구한테 설명하듯 쉽고 재미있게 알려줄게. 따라와!

들어가기 전에: OAuth 2.0, 대체 그게 뭔데?

야, 너 혹시 이런 경험 없어?
완전 처음 보는 웹사이트나 앱에서 "서비스를 이용하려면 회원가입하세요" 대신 "구글/카카오/네이버로 로그인하기" 버튼이 떡하니 있는 거.
그거 누르면 익숙한 구글 로그인 창이 뜨고, 아이디 비번 치고 "동의" 버튼 누르면 바로 로그인이 되잖아. 완전 편하지?

이때 우리 아이디랑 비밀번호가 그 처음 보는 앱에 직접 넘어가는 게 아니거든. 만약 그랬다면? 끔찍하지. 내 구글 계정이 통째로 털릴 수도 있는 거잖아.
바로 이럴 때 마법처럼 등장하는 게 **OAuth 2.0**이야.

OAuth 2.0을 한마디로 정의하면 '인증을 위한 개방형 표준 프로토콜'인데, 말이 너무 어렵지?
쉽게 말해 **"내 비밀번호를 직접 주지 않고도, 다른 앱에게 내 정보에 접근할 수 있는 권한을 안전하게 위임하는 방법"**이라고 생각하면 돼.

마치 내가 호텔에 묵을 때, 내 방 마스터키를 주는 게 아니라 내 방만 열 수 있는 '카드키'를 발급해 주는 것과 같아.
카드키로는 내 방 문만 열 수 있지, 호텔 금고를 열거나 다른 방에 들어갈 수는 없잖아?
OAuth 2.0은 바로 이런 '제한된 권한을 가진 카드키(Access Token)'를 발급하고 관리하는 규칙들의 집합이야.

OAuth 2.0의 핵심 플레이어들

이 복잡한 인증 과정을 이해하려면 먼저 등장인물부터 알아야 해. 총 4명의 플레이어가 있어. 이걸 클럽 입장하는 상황에 비유해볼게. 재밌겠지?

  1. 리소스 소유자 (Resource Owner): 바로 '나', 사용자야. 내 데이터(프로필, 이메일, 사진 등)의 주인이지. 클럽에 들어가고 싶은 '손님'이야.
  2. 클라이언트 (Client): 내 정보를 이용하려는 서비스나 앱. 예를 들어 '재능넷'이 구글 로그인을 지원한다면, 여기서 '재능넷'이 클라이언트가 되는 거야. 손님을 클럽에 데려가려는 '파티 플래너'라고 생각하자.
  3. 인증 서버 (Authorization Server): 사용자인 내가 "이 앱 믿을만하니, 내 정보에 접근할 권한을 주세요"라고 요청하고, 허락을 받는 곳. 구글, 카카오, 네이버의 로그인 서버가 여기에 해당해. 클럽 입장을 허가하고 '입장 팔찌'를 채워주는 '가드' 역할이야.
  4. 리소스 서버 (Resource Server): 내 실제 데이터(구글 프로필, 카카오 친구 목록 등)가 저장된 서버. 클라이언트는 인증 서버에서 받은 '카드키'를 가지고 리소스 서버에 가서 데이터를 요청해. 클럽 내부의 '바(Bar)' 같은 곳이지. 입장 팔찌를 보여줘야 칵테일(데이터)을 내어주잖아.
OAuth 2.0 인증 흐름도 리소스 소유자 (사용자) 클라이언트 (서비스 애플리케이션) 인증 서버 (Google, Kakao 등) 리소스 서버 (사용자 데이터 저장소) 1. 로그인 / 권한 요청 2. 사용자 인증 및 동의 3. 권한 증표(Token) 전달 4. 리소스(데이터) 요청 5. 리소스 전달

이 네 명의 플레이어가 어떤 순서와 방법으로 '카드키(Access Token)'를 주고받느냐에 따라 여러 가지 시나리오, 즉 **'Grant Type(권한 부여 방식)'**이 나뉘는 거야. 이제부터 본격적으로 이 Grant Type들을 하나씩 파헤쳐 보자고!


1. 인증 코드 그랜트 (Authorization Code Grant): 가장 안전한 정석 플로우

자, 첫 번째 주자는 바로 **인증 코드 그랜트**야.
이름부터 뭔가 '정석'의 느낌이 나지? 맞아. OAuth 2.0에서 가장 많이 쓰이고, 가장 안전하다고 평가받는 방식이야. 특히 백엔드 서버가 있는 '전통적인' 웹 애플리케이션에 최적화되어 있어.

이 방식의 핵심은 **"클라이언트(앱)가 브라우저에 직접 Access Token을 노출시키지 않는다"**는 거야.
대신 '임시 비밀번호' 같은 **'인증 코드(Authorization Code)'**를 중간에 한 번 거쳐가지. 이 과정 때문에 보안성이 확 올라가.

이 흐름을 온라인 쇼핑 후 택배 받는 과정에 비유해볼게.

  • 나(Resource Owner): 쇼핑몰에서 물건을 주문한 고객
  • 쇼핑몰 앱(Client): 내가 주문한 쇼핑몰
  • 택배사 인증 시스템(Authorization Server): 고객 정보와 운송장 번호를 관리하는 시스템
  • 택배 물류창고(Resource Server): 실제 내 물건이 보관된 곳

인증 코드 그랜트, 단계별로 따라가 보기

STEP 1: 권한 부여 요청 (Authorization Request)

내가 쇼핑몰 앱에서 '구글 계정으로 로그인' 버튼을 딱 눌렀어.
그러면 내 브라우저는 구글 로그인 페이지로 이동(리디렉션)하게 돼. 이때 쇼핑몰 앱은 그냥 보내는 게 아니라, URL에 중요한 정보들을 쿼리 파라미터로 담아서 보내.

GET https://accounts.google.com/o/oauth2/v2/auth?
 response_type=code&
 client_id=YOUR_CLIENT_ID&
 scope=openid%20profile%20email&
 redirect_uri=https://oauth.my-awesome-app.com/callback
  • response_type=code: "인증 코드 그랜트 방식으로 진행할게요! 그러니 나중에 **인증 코드(code)**를 주세요." 라는 뜻.
  • client_id: 쇼핑몰 앱이 구글에 미리 등록하고 받은 고유 식별자. "안녕하세요, 저 '재능넷'인데요." 하고 신분증을 보여주는 것과 같아.
  • scope: "제가 사용자의 어떤 정보에 접근하고 싶은데요, 허락해주세요." 라고 요청하는 권한의 범위야. 여기서는 '프로필'과 '이메일' 정보를 요청하고 있네.
  • redirect_uri: 구글이 인증 절차를 끝내고 사용자를 다시 돌려보낼 쇼핑몰 앱의 주소. 보안을 위해 이 주소는 구글에 미리 등록된 주소와 정확히 일치해야 해.

STEP 2: 사용자 인증 및 동의

이제 내 화면에는 익숙한 구글 로그인 창이 떠. 나는 내 구글 아이디와 비밀번호를 입력해서 로그인해.
로그인이 성공하면 구글은 나에게 물어봐. **"이 '쇼핑몰 앱'이 당신의 프로필과 이메일 정보에 접근하려고 하는데, 허락하시겠습니까?"**

이 화면이 바로 '동의 화면(Consent Screen)'이야. 내가 여기서 '동의' 버튼을 누르는 행위가 바로 리소스 소유자(나)가 클라이언트(쇼핑몰 앱)에게 권한을 위임하는 핵심적인 순간이지.

STEP 3: 인증 코드 발급

내가 '동의'를 누르면, 구글(인증 서버)은 약속대로 내 브라우저를 아까 쇼핑몰 앱이 알려준 redirect_uri로 다시 돌려보내.
이때! URL 뒤에 **'인증 코드(Authorization Code)'**를 붙여서 보내줘. 마치 택배 운송장 번호를 발급해주는 것과 같아.

https://oauth.my-awesome-app.com/callback?code=4/0AY0e-g78...

이 `code`는 수명이 매우 짧고(보통 몇 분 이내) 딱 한 번만 사용할 수 있는 일회성 코드야. 만약 이 코드가 중간에 탈취되더라도, 공격자가 이걸로 바로 내 정보에 접근할 수는 없어. 왜냐하면 다음 단계가 남아있기 때문이지.

STEP 4: Access Token 교환 요청 (백엔드 통신)

자, 이제 쇼핑몰 앱의 **백엔드 서버**가 나설 차례야. 이게 이 방식의 가장 중요한 포인트야.
브라우저(프론트엔드)는 백엔드 서버에게 방금 받은 `code`를 전달해. 그러면 백엔드 서버는 이 `code`와 함께 자신의 비밀 열쇠인 `client_secret`을 포함해서, 구글의 토큰 발급 엔드포인트에 직접 요청을 보내. 이 통신은 사용자의 브라우저를 거치지 않고 서버 대 서버로 은밀하게 이루어져.

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=4/0AY0e-g78...&
redirect_uri=https://oauth.my-awesome-app.com/callback&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET
  • grant_type=authorization_code: "저 인증 코드 가져왔으니, 이제 진짜 카드키(Access Token)로 바꿔주세요."
  • code: 방금 브라우저를 통해 전달받은 일회성 인증 코드.
  • client_secret: 쇼핑몰 앱의 비밀키. 이 비밀키를 알고 있다는 건, 이 요청이 진짜 쇼핑몰 앱의 서버에서 온 것임을 증명하는 강력한 수단이야.

STEP 5: Access Token 발급

구글(인증 서버)은 쇼핑몰 앱이 보낸 `code`, `client_id`, `client_secret` 등이 모두 유효한지 확인해. 모든 게 완벽하다면, 드디어 최종 카드키인 **'Access Token'**과, 필요에 따라 'Refresh Token'을 발급해서 쇼핑몰 앱의 백엔드 서버에게 응답으로 보내줘.

{
  "access_token": "ya29.a0AfH6... (이게 진짜 카드키!)",
  "expires_in": 3599,
  "refresh_token": "1//04G... (카드키 유효기간 만료 시 재발급용)",
  "scope": "openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
  "token_type": "Bearer"
}

이제 쇼핑몰 앱의 백엔드 서버는 이 `access_token`을 안전하게 저장해 둬. (예: 데이터베이스나 세션)

STEP 6: 리소스 접근 (API 호출)

드디어 쇼핑몰 앱은 발급받은 `access_token`을 가지고 구글의 리소스 서버(예: Google People API)에 내 정보를 요청할 수 있게 됐어.
요청을 보낼 때 HTTP 헤더에 이 토큰을 포함시켜서 보내. "저 카드키 가지고 왔으니, 이 사람 프로필 정보 좀 주세요!" 하는 거지.

GET https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses
Authorization: Bearer ya29.a0AfH6...

구글 리소스 서버는 이 토큰이 유효한지 확인하고, 유효하다면 요청받은 내 이름과 이메일 주소를 쇼핑몰 앱에게 전달해줘. 쇼핑몰 앱은 이 정보를 받아서 회원가입을 자동으로 처리하거나, 로그인 상태로 만들어주는 거야. 모든 과정이 끝났어!

OAuth 2.0 Authorization Code Flow 사용자 브라우저 클라이언트 (프론트) 클라이언트 (백엔드) 인증 서버 리소스 서버 1. 로그인 요청 2. 인증 요청 (response_type=code) 3. 로그인 & 동의 후 redirect_uri로 인증 코드 전달 4. 인증 코드를 백엔드로 전달 5. 인증코드, client_secret으로 Access Token 요청 6. Access Token 발급 7. Access Token으로 리소스(API) 요청 8. 리소스 응답

언제 사용해야 할까?

✅ 백엔드 서버가 있는 웹 애플리케이션 (가장 일반적인 케이스)
✅ 보안이 매우 중요한 서비스
✅ Access Token의 유효기간이 만료되었을 때 Refresh Token을 사용해 자동으로 재발급받는 등, 장기적인 세션 관리가 필요할 때

장점과 단점

👍 장점: Access Token이 사용자의 브라우저에 노출되지 않고, 서버 간의 안전한 통신을 통해 발급되므로 보안성이 매우 높다. Refresh Token을 사용할 수 있어 사용자 경험을 해치지 않으면서도 토큰을 안전하게 관리할 수 있다.
👎 단점: 흐름이 다른 방식에 비해 다소 복잡하고, 백엔드 서버에서 처리해야 할 로직이 추가된다.


2. 암시적 그랜트 (Implicit Grant): 빠르지만 위험한 프론트엔드 전용 플로우

다음 주자는 **암시적 그랜트**야. 이 친구는 인증 코드 그랜트와는 성격이 좀 달라.
한마디로 "백엔드 서버 없이, 프론트엔드(자바스크립트)만으로 모든 걸 처리하고 싶을 때"를 위해 태어난 방식이야. 그래서 **Single Page Application(SPA)** 초창기에 많이 사용됐어.

이름이 왜 '암시적'이냐면, 인증 코드를 발급하고 토큰으로 교환하는 명시적인 단계가 없기 때문이야.
그냥 인증 서버가 "ㅇㅋ 너 통과!" 하면서 Access Token을 URL에 냅다 던져줘. 그래서 과정이 엄청 단순하고 빨라.

하지만... 세상에 공짜는 없지. 편한 만큼 치명적인 보안 약점을 가지고 있어. 그래서 요즘은 거의 사용하지 않고, 다른 방식으로 대체되고 있는 추세야. 왜 그런지 한번 흐름을 따라가 보자.

암시적 그랜트, 빛과 그림자

STEP 1: 권한 부여 요청

시작은 인증 코드 그랜트와 비슷해. 사용자가 '로그인' 버튼을 누르면, 클라이언트 앱이 인증 서버로 사용자를 리디렉션시켜.
그런데 결정적인 차이가 있어. response_type이 code가 아니라 token이야.

GET https://accounts.google.com/o/oauth2/v2/auth?
 response_type=token&
 client_id=YOUR_CLIENT_ID&
 scope=openid%20profile%20email&
 redirect_uri=https://my-spa-app.com/callback

response_type=token: "안녕하세요! 저 백엔드 없어요. 복잡한 거 말고 그냥 바로 **Access Token**을 주세요." 라는 강력한 의사 표현이야.

STEP 2: 사용자 인증 및 동의

이 단계는 인증 코드 그랜트와 완전히 똑같아. 사용자는 인증 서버(구글, 카카오 등)에 로그인하고, "이 앱이 내 정보에 접근하는 것을 허용할까요?" 라는 물음에 '동의'를 눌러.

STEP 3: Access Token 직접 발급

자, 여기서부터 완전히 달라져.
사용자가 동의하면, 인증 서버는 인증 코드를 주는 대신 **Access Token을 직접 만들어서** redirect_uri로 보내줘.
근데 그냥 보내는 게 아니라, URL의 **프래그먼트(fragment)**, 즉 # 뒤에 붙여서 보내줘.

https://my-spa-app.com/callback#access_token=xyZ123...&token_type=Bearer&expires_in=3600

왜 하필 프래그먼트일까? URL 프래그먼트는 서버로 전송되지 않는다는 특징이 있어. 오직 클라이언트 측의 브라우저만 읽고 처리할 수 있지. 그래서 자바스크립트로 이 값을 파싱해서 토큰을 획득하는 거야.

이러면 끝이야! 백엔드 서버와의 통신 같은 복잡한 과정이 전혀 없지. SPA 앱은 자바스크립트로 URL에서 토큰을 쏙 빼내서 변수에 저장하고, 바로 이 토큰을 이용해서 리소스 서버에 API 요청을 보낼 수 있어.

사용자 (Resource Owner) 클라이언트 (SPA) 인증 서버 (Authorization Server) 리소스 서버 (Resource Server) 1. 로그인 요청 2. 인증 요청 (response_type=token) 3. Access Token 전달 (in URL Fragment) 4. 클라이언트가 URL에서 토큰 파싱 5. API 요청 (with Access Token) 6. 리소스 응답

그래서 뭐가 문제인데? (치명적 단점)

🚨 보안 경고: 암시적 그랜트는 이제 쓰지 마세요!

편리함의 대가는 생각보다 컸어. 암시적 그랜트는 다음과 같은 심각한 보안 취약점을 가지고 있어.

  1. 토큰 탈취 위험: Access Token이 URL 프래그먼트에 직접 노출돼. 악의적인 브라우저 확장 프로그램이나, 다른 스크립트를 통해 이 URL을 훔쳐볼 수 있다면 토큰이 그대로 탈취될 수 있어 (XSS 공격). 또한, 브라우저 히스토리에 토큰이 남을 수도 있고, 다른 사람에게 링크를 복사/붙여넣기 하다가 토큰이 유출될 수도 있지.
  2. 토큰 위조 공격 (CSRF): 공격자가 위조된 토큰을 사용자에게 주입하여 의도치 않은 동작을 유발할 수도 있어.
  3. Refresh Token 사용 불가: 보안상의 이유로, 암시적 그랜트 방식으로는 Refresh Token을 발급받을 수 없어. 즉, Access Token의 유효기간이 만료되면 사용자는 무조건 다시 로그인해서 인증 과정을 처음부터 거쳐야 해. 사용자 경험에 좋지 않겠지?

이러한 이유로, IETF(국제 인터넷 표준화 기구)의 최신 OAuth 2.0 보안 모범 사례(BCP)에서는 암시적 그랜트의 사용을 권장하지 않아.

그럼 대안은? 👉 **PKCE를 사용한 인증 코드 그랜트**

"아니, 그럼 백엔드 없는 SPA나 모바일 앱은 어떡하라고?" 라는 질문이 바로 나오겠지?
걱정 마. 이 문제를 해결하기 위해 등장한 구원투수가 바로 **PKCE(Proof Key for Code Exchange)** 야. (뒤에서 자세히 다룰 거야!)

PKCE는 인증 코드 그랜트 방식에 추가적인 보안 계층을 더해서, client_secret이 없는 '공개 클라이언트(Public Client)'에서도 안전하게 인증 코드를 토큰으로 교환할 수 있게 해줘.
그래서 오늘날의 SPA나 모바일 앱은 **암시적 그랜트 대신 'PKCE를 적용한 인증 코드 그랜트'를 사용하는 것이 표준**이야.


3. 리소스 소유자 암호 자격증명 그랜트 (Resource Owner Password Credentials Grant): 믿는 사이에만 쓰는 구식 방법

자, 이번엔 이름부터 좀 무시무시한 친구야. **리소스 소유자 암호 자격증명 그랜트**.
이름이 너무 길어서 보통 **'Password Grant'**라고 불러. 이 방식은 OAuth 2.0의 근본 철학을 정면으로 위배하는, 아주 특별하고 위험한 방식이야.

OAuth 2.0의 핵심이 뭐였지? "내 비밀번호를 직접 주지 않고 권한을 위임하는 것"이었잖아.
근데 이 방식은... **클라이언트 앱에 사용자의 아이디와 비밀번호를 직접 입력받아.**

...엥? 그럼 그냥 옛날 로그인 방식이랑 뭐가 다른데? 싶지?
맞아. 그래서 이 방식은 정말, 정말, 아주 예외적인 상황에서만 사용해야 해.

이 흐름을 비유하자면, 내가 내 집 열쇠를 친구에게 그냥 줘버리는 것과 같아.
그 친구를 100% 신뢰하니까 가능한 일이지, 처음 보는 사람한테 내 집 열쇠를 줄 순 없잖아?

Password Grant, 어떻게 돌아갈까?

STEP 1: 사용자 자격증명 수집

클라이언트 앱(예: 공식 트위터 모바일 앱)의 로그인 화면에 사용자가 자신의 트위터 아이디와 비밀번호를 직접 입력해.

STEP 2: Access Token 요청

클라이언트 앱은 사용자가 입력한 아이디(username)와 비밀번호(password)를 받아서, 자신의 client_id, client_secret과 함께 인증 서버의 토큰 엔드포인트로 바로 쏴버려.

POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded

grant_type=password&
username=USER_NAME&
password=USER_PASSWORD&
client_id=CLIENT_ID

grant_type=password: "저 사용자한테 직접 아이디랑 비번 받았어요. 이거 맞는지 확인하고 Access Token 주세요." 라는 뜻. 브라우저 리디렉션 같은 거 전혀 없어. 그냥 서버 간의 API 호출 한 방으로 끝나.

STEP 3: Access Token 발급

인증 서버는 전달받은 아이디와 비밀번호가 올바른지 확인해. 맞다면, 바로 Access Token을 발급해서 클라이언트 앱에게 돌려줘.
이제 클라이언트 앱은 이 토큰으로 리소스 서버에 접근할 수 있게 되는 거지.

OAuth 2.0: Password Credentials Grant Flow 사용자 클라이언트 앱 인증 서버 리소스 서버 1. ID/PW 제출 2. 토큰 요청 (Password Grant) 3. Access Token 발급 4. API 요청 (Access Token 첨부) 5. 보호된 자원 응답

절대 쓰면 안 될 것 같은데... 언제 쓰는 거야?

맞아, 웬만하면 쓰면 안 돼. 제3자 앱(Third-party application)이 이 방식을 쓴다면 그건 100% 사기(피싱)라고 봐도 무방해.
하지만 이 방식이 존재하는 데는 이유가 있겠지? 딱 두 가지 경우에만 제한적으로 사용이 고려돼.

⚠️ Password Grant 사용이 허용되는 매우 예외적인 경우

  1. 클라이언트가 서비스와 완전히 동일한 시스템일 때 (First-party Client):
    예를 들어, 구글이 만든 'Gmail' 앱에서 내 구글 계정으로 로그인하는 경우야. 사용자는 구글을 신뢰하고, Gmail 앱도 구글이 직접 만들었으니 내 비밀번호를 맡겨도 안전하다고 믿을 수 있지. 이처럼 **서비스 제공자와 클라이언트 앱 개발사가 동일하고, 사용자와 높은 신뢰 관계가 구축된 경우**에 한해 사용될 수 있어.
  2. 레거시 시스템 마이그레이션:
    원래 아이디/비밀번호 기반으로 로그인하던 아주 오래된 시스템을 OAuth 2.0 기반으로 점진적으로 전환할 때, 임시방편으로 사용될 수 있어. 사용자들에게 갑자기 로그인 방식을 바꾸라고 할 수 없으니, 기존 방식을 유지하면서 내부적으로는 OAuth 토큰을 발급하는 거지.

결론: 당신이 만들고 있는 서비스가 위 두 가지 케이스에 해당하지 않는다면, 이 방식은 그냥 잊어버리는 게 정신 건강에 이로워.


번외: 클라이언트 자격증명 그랜트 (Client Credentials Grant) - 기계들만의 대화법

지금까지 다룬 세 가지 방식은 모두 '사용자(Resource Owner)'가 관여하는 흐름이었어.
그런데 만약 **사용자 없이, 서비스(기계)와 서비스(기계)가 서로 통신**해야 할 때는 어떡할까?

예를 들어, 내 쇼핑몰의 '주문 처리 백엔드 서버'가 '재고 관리 백엔드 서버'에게 "이 상품 재고 몇 개 남았어?"라고 물어봐야 하는 상황을 생각해봐.
여기엔 사용자가 끼어들 틈이 없지? 이럴 때 사용하는 것이 바로 **클라이언트 자격증명 그랜트**야.

이 방식은 클라이언트 자체가 하나의 '사용자'처럼 행동해. 자신의 `client_id`와 `client_secret`을 자격증명으로 사용해서 Access Token을 발급받지. 이 토큰은 특정 사용자의 데이터가 아니라, 클라이언트 앱이 접근하도록 허용된 자원(API)에 대한 권한을 나타내.

M2M(Machine-to-Machine) 통신의 정석

STEP 1: Access Token 요청

클라이언트(예: 주문 처리 서버)가 인증 서버에 자신의 `client_id`와 `client_secret`을 보내면서 토큰을 요청해. 사용자의 아이디/비번 같은 건 당연히 없어.

POST /token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET

grant_type=client_credentials: "저 사용자 아니구요, 클라이언트 본인인데요. 제 신분증(ID, Secret) 확인하시고 저한테 권한 좀 주세요." 라는 뜻.

STEP 2: Access Token 발급

인증 서버는 클라이언트의 자격증명이 유효한지 확인하고, 이 클라이언트에게 부여된 권한을 담은 Access Token을 발급해줘.
이제 주문 처리 서버는 이 토큰을 가지고 재고 관리 서버의 API를 안전하게 호출할 수 있게 되는 거야.

언제 사용해야 할까?

✅ 사용자의 개입 없이 서버 간, 또는 서비스 간 통신(M2M)이 필요할 때
✅ 마이크로서비스 아키텍처(MSA)에서 각 서비스들이 서로의 API를 호출할 때
✅ 외부 서비스에 제공하는 API의 접근 권한을 관리할 때


최종 보스 등장: PKCE - SPA와 모바일 앱을 위한 구원투수

자, 아까 암시적 그랜트의 대안으로 잠깐 언급했던 **PKCE**를 드디어 제대로 다룰 시간이야.
PKCE는 '픽시'라고 읽고, **Proof Key for Code Exchange**의 약자야. 이름 한번 멋지지? '코드 교환을 위한 증명 키'라니.

PKCE는 원래 모바일 앱처럼 `client_secret`을 안전하게 저장할 수 없는 '공개 클라이언트'를 위해 고안됐어.
생각해봐. 웹 백엔드 서버는 `client_secret`을 소스코드나 환경 변수에 꽁꽁 숨겨둘 수 있지만, 모바일 앱이나 SPA는 어때?
자바스크립트 코드나 앱 파일은 사용자가 마음만 먹으면 다 뜯어볼 수 있잖아. 여기에 `client_secret`을 넣는 건 "비밀번호는 1234입니다"라고 써 붙여놓는 거나 마찬가지야.

그래서 공개 클라이언트는 `client_secret` 없이 인증 코드 그랜트를 사용해야 하는데, 여기엔 한 가지 허점이 있어. 바로 **'인증 코드 가로채기(Authorization Code Interception Attack)'** 공격이야.

악성 앱이 내 폰에 깔려있다가, 내가 정상적인 앱으로 로그인을 시도할 때 리디렉션되는 과정에서 '인증 코드'를 중간에 낚아채는 거지. 그리고 그 코드를 가지고 자기 서버에서 토큰을 발급받아 내 정보를 털어갈 수 있어. `client_secret`이 없으니 인증 서버는 이 요청이 정상 앱에서 온 건지, 악성 앱에서 온 건지 구분할 방법이 없거든.

PKCE는 바로 이 문제를 해결하기 위해 등장했어.
핵심 아이디어는 **"인증 코드를 요청한 놈과, 그 코드로 토큰을 요청하는 놈이 같은 놈인지 확인하자!"**는 거야.

PKCE, 어떻게 공격을 막아낼까?

PKCE는 인증 코드 그랜트 흐름에 딱 두 단계만 추가하면 돼.

STEP 1 (추가): Code Verifier와 Code Challenge 생성

인증을 시작하기 전에, 클라이언트 앱(SPA, 모바일 앱)이 먼저 **임의의 비밀 문자열**을 하나 만들어. 이걸 **`code_verifier`**라고 불러. 이건 정말 나만 아는 비밀이야.

code_verifier = "aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890..." (랜덤하고 긴 문자열)

그 다음, 이 `code_verifier`를 **SHA256 해시 함수**로 암호화하고, **Base64URL 인코딩**을 적용해. 이렇게 변환된 값을 **`code_challenge`**라고 불러. 이건 공개돼도 괜찮은 값이야.

code_challenge = BASE64URL-ENCODE(SHA256(code_verifier))

STEP 2 (변경): 인증 요청 시 Code Challenge 전달

이제 인증 서버로 권한 부여를 요청할 때, 아까 만든 `code_challenge`와 어떤 방식으로 암호화했는지 알려주는 `code_challenge_method`를 파라미터에 추가해서 보내.

GET https://accounts.google.com/o/oauth2/v2/auth?
 response_type=code&
 client_id=YOUR_CLIENT_ID&
 ...
 &code_challenge=E9Mel...&
 &code_challenge_method=S256

인증 서버는 이 `code_challenge`를 일단 저장해 둬. 그리고 평소처럼 로그인/동의 절차를 거쳐 클라이언트에게 **인증 코드**를 발급해줘.

STEP 3 (변경): 토큰 요청 시 Code Verifier 전달

클라이언트가 인증 코드를 받아서 토큰을 요청할 차례야. 이때! 토큰 요청 파라미터에 아까 맨 처음에 만들었던, 나만 아는 비밀 문자열 **`code_verifier`**를 추가해서 보내.

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=4/0AY0e-g78...&
redirect_uri=...&
client_id=YOUR_CLIENT_ID&
code_verifier=aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890...

여기엔 `client_secret`이 없다는 걸 주목해!

STEP 4 (추가): 서버에서 검증

인증 서버는 토큰 요청을 받으면, 함께 온 `code_verifier`를 이전에 저장해 뒀던 `code_challenge_method`(S256) 방식으로 똑같이 변환해 봐.
그리고 그 결과가 처음에 받았던 `code_challenge`와 일치하는지 비교하는 거야.

만약 두 값이 일치한다? "아하! 인증 코드를 요청했던 그 클라이언트가 맞구나!"라고 확신하고 안전하게 Access Token을 발급해줘.
만약 일치하지 않는다면? "이놈은 코드를 훔쳐 온 가짜구나!"라고 판단하고 요청을 거부해버리지.

공격자가 중간에 인증 코드를 가로채더라도, 원본 `code_verifier`를 모르기 때문에 절대 토큰을 발급받을 수 없게 되는 거야. 완벽한 방어지!

PKCE, 이제는 선택이 아닌 필수!

PKCE 덕분에 `client_secret`을 안전하게 보관할 수 없는 SPA, 네이티브 앱 같은 공개 클라이언트들도 인증 코드 그랜트의 높은 보안성을 누릴 수 있게 됐어.
그래서 **오늘날 새로운 SPA나 모바일 앱을 개발한다면, 암시적 그랜트가 아니라 반드시 PKCE를 적용한 인증 코드 그랜트를 사용해야 해.**
이건 그냥 권장 사항이 아니라, 사실상 업계 표준이라고 생각하면 돼.


최종 정리: 그래서 뭘 써야 할까? 한눈에 보는 결정 가이드

자, 지금까지 정신없이 여러 가지 Grant Type을 살펴봤어. 머리가 좀 복잡할 수 있으니 마지막으로 깔끔하게 정리해줄게.
내 상황에 어떤 방식을 써야 할지 고민될 때 이 표를 참고해봐!

Grant Type (권한 부여 방식) 주요 사용처 보안 수준 핵심 특징 한 줄 요약
인증 코드 그랜트
(Authorization Code)
백엔드 서버가 있는 웹 애플리케이션 (예: PHP, Java, Node.js 기반 웹사이트) 매우 높음 - Access Token이 서버 간 통신으로만 전달됨
- Client Secret으로 클라이언트 인증
- Refresh Token 사용 가능
가장 표준적이고 안전한 방식. 웹 개발의 정석.
인증 코드 + PKCE 싱글 페이지 앱 (SPA), 모바일 앱 (iOS, Android), 데스크톱 앱 등 '공개 클라이언트' 매우 높음 - Client Secret 없이 인증 코드 그랜트 사용
- Code Verifier/Challenge로 코드 탈취 방지
현대 프론트엔드/모바일 개발의 표준.
암시적 그랜트
(Implicit)
(과거) 백엔드 없는 SPA 낮음 - Access Token이 URL에 직접 노출됨
- Refresh Token 사용 불가
- 흐름이 단순함
보안 취약점으로 인해 더 이상 사용하지 않음 (Deprecated).
리소스 소유자 암호 자격증명
(Password)
서비스 제공사가 직접 만든 앱 (First-party), 레거시 시스템 마이그레이션 매우 위험 - 클라이언트가 사용자의 ID/PW를 직접 받음
- OAuth 철학에 위배됨
신뢰 관계가 100% 확실할 때만 쓰는 최후의 수단.
클라이언트 자격증명
(Client Credentials)
서버 간 통신 (M2M), 마이크로서비스 아키텍처, 자동화된 프로세스 높음 - 사용자가 개입하지 않음
- 클라이언트 자체가 인증 주체
기계들끼리 소통할 때 쓰는 방식.

어때? 이제 좀 감이 오지?
OAuth 2.0은 처음 배울 때 용어도 많고 흐름도 복잡해서 어렵게 느껴질 수 있어. 하지만 그 원리를 이해하고 나면 왜 이렇게 설계되었는지, 각 방식이 어떤 문제를 해결하기 위해 존재하는지 명확하게 보일 거야.

결국 OAuth 2.0의 핵심은 **'최소 권한의 원칙'**과 **'안전한 위임'**이야. 사용자에게는 신뢰를, 개발자에게는 강력한 보안과 확장성을 제공하는 현대 웹의 필수 교양이지. 오늘 배운 내용을 바탕으로 직접 소셜 로그인을 구현해보거나 API 연동을 시도해본다면, 분명 더 깊이 이해할 수 있을 거야. 혹시 구현하다가 막히는 부분이 생긴다면, **재능넷** 같은 플랫폼에서 경험 많은 개발자에게 조언을 구하는 것도 아주 좋은 방법이 될 수 있어. 혼자 끙끙 앓는 것보다 훨씬 빠르고 효과적일 테니까!

자, 그럼 오늘 스터디는 여기까지! 모두들 해피 코딩!

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.