BioPlayground

🧬
목록으로

OAuth 2.0 — 제3자 인증의 원리

OAuth 2.0이 무엇인지, 왜 비밀번호를 직접 받지 않는지, Authorization Code Flow가 어떻게 작동하는지 배웁니다.

중급
|
14
|
검증 완료 (2026-07)
OAuth소셜 로그인인증 흐름Authorization CodeAccess Token
진행률0/26 (0%)

OAuth 2.0 — 제3자 인증의 원리

이 토픽을 마치면

OAuth 2.0이 해결하는 문제, Authorization Code Flow의 전체 흐름, 그리고 Access Token과 Refresh Token의 역할을 이해할 수 있습니다.


비밀번호를 왜 안 받나

"구글로 로그인", "카카오로 로그인" 버튼을 누르면 우리 서비스에 비밀번호를 입력하지 않습니다. 구글 화면으로 이동해서 구글에 로그인하고, 다시 돌아옵니다.

왜 이렇게 복잡하게 만들었을까요? 간단합니다 — 비밀번호를 남의 서버에 넘기면 안 되기 때문입니다.

만약 어떤 서비스가 "구글 계정 이메일과 비밀번호를 입력하세요"라고 한다면, 그 서비스가 내 비밀번호를 저장할 수 있습니다. 유출되면 구글 계정이 통째로 뚫립니다. OAuth는 이 문제를 해결합니다 — 비밀번호 대신 "허가증"을 주는 것입니다.


4명의 등장인물

OAuth에는 역할이 4개 있습니다:

역할의미예시
Resource Owner사용자 (데이터의 주인)
Client우리가 만든 서비스우리 웹앱
Authorization Server인증을 담당하는 서버구글 인증 서버
Resource Server사용자 데이터가 있는 서버구글 프로필 API

이름이 직관적이지 않습니다. "Client"가 사용자가 아니라 우리 서버라는 점을 주의하세요. OAuth 관점에서 우리 서비스가 구글에게 "데이터 좀 주세요"라고 요청하는 입장이라 Client입니다.


Authorization Code Flow

가장 많이 쓰이는 흐름입니다. 단계별로 보겠습니다:

text
1. 사용자가 "구글로 로그인" 클릭
2. 우리 서버가 구글 인증 서버로 리다이렉트
   → URL에 client_id, redirect_uri, scope 포함
3. 사용자가 구글에서 로그인 + "이 앱에 허용" 클릭
4. 구글이 redirect_uri로 "인가 코드(code)" 전달
5. 우리 서버가 인가 코드 + client_secret으로 구글에 요청
6. 구글이 Access Token 발급
7. 우리 서버가 Access Token으로 사용자 정보 요청

핵심은 두 번 교환한다는 것입니다. 인가 코드(일회용)를 먼저 받고, 그걸로 진짜 토큰을 받습니다. 왜 한 번에 토큰을 안 줄까요?

4단계에서 인가 코드는 URL 파라미터로 전달됩니다. URL은 브라우저 히스토리에 남고, 로그에 찍힙니다. 만약 여기에 토큰이 들어있으면 누군가 볼 수 있습니다. 인가 코드는 일회용이고 짧은 시간 내에 만료되니까, 유출되어도 피해가 제한됩니다.

5단계에서 진짜 토큰 교환은 서버 간 통신(백채널)으로 일어납니다. client_secret이 포함되어 있고, 이건 절대 브라우저에 노출되지 않습니다.


Access Token과 Refresh Token

javascript
// 구글이 응답하는 토큰 구조 (예시)
{
  "access_token": "ya29.a0AfH6SM...",
  "expires_in": 3600,          // 1시간
  "refresh_token": "1//0eXyz...",
  "token_type": "Bearer",
  "scope": "email profile"
}
토큰수명용도
Access Token짧음 (보통 1시간)API 호출에 사용
Refresh Token김 (수주~수개월)Access Token 재발급용

Access Token이 만료되면 Refresh Token으로 새 Access Token을 받습니다. 매번 사용자에게 "다시 로그인하세요"라고 하지 않아도 됩니다.

왜 Access Token을 길게 안 만들까요? 토큰이 유출되었을 때 피해 기간을 최소화하기 위해서입니다. 1시간짜리 토큰이 유출되면 1시간만 위험하지만, 1년짜리 토큰이 유출되면 1년간 위험합니다.


scope — 권한의 범위

text
https://accounts.google.com/o/oauth2/v2/auth?
  client_id=OUR_CLIENT_ID&
  redirect_uri=http://localhost:3000/callback&
  scope=email+profile&
  response_type=code

scope어디까지 접근할 수 있는지 정하는 것입니다. email profile이면 이메일과 프로필 사진만 가져올 수 있고, 구글 드라이브 파일은 못 봅니다.

사용자가 "이 앱에 허용" 클릭할 때 보이는 화면에 "이 앱이 귀하의 이메일 주소를 확인합니다"라고 나오는 게 scope입니다.

최소 권한 원칙 — 필요한 것만 요청하세요. "모든 권한"을 요청하면 사용자가 겁먹고 거부합니다.


우리 서비스에서 구현할 때

실제 구현 시 알아야 할 것:

  1. Provider 등록: 구글/카카오 개발자 콘솔에서 앱을 등록하고 client_idclient_secret을 받습니다
  2. redirect_uri 설정: 인가 코드를 받을 콜백 URL을 등록합니다 — 등록되지 않은 URL로는 리다이렉트가 거부됩니다
  3. 상태값(state) 검증: CSRF 방지용 랜덤 문자열을 요청에 포함하고, 콜백에서 일치 여부를 확인합니다
javascript
// Express에서의 콜백 처리 (개념)
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // 1. state 검증 (CSRF 방지)
  if (state !== req.session.oauthState) {
    return res.status(403).send('Invalid state');
  }

  // 2. 인가 코드로 토큰 교환 (서버 → 구글, 백채널)
  const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    body: new URLSearchParams({
      code,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      redirect_uri: REDIRECT_URI,
      grant_type: 'authorization_code',
    }),
  });

  const { access_token } = await tokenRes.json();

  // 3. Access Token으로 사용자 정보 요청
  const userRes = await fetch(
    'https://www.googleapis.com/oauth2/v2/userinfo',
    { headers: { Authorization: `Bearer ${access_token}` } }
  );
  const user = await userRes.json();

  // 4. 세션에 사용자 저장
  req.session.user = user;
  res.redirect('/');
});

핵심

OAuth 2.0은 비밀번호 대신 토큰으로 권한을 위임하는 프로토콜입니다. Authorization Code Flow는 인가 코드(일회용) → Access Token(단기) → API 호출의 두 단계 교환입니다. Access Token은 짧게, Refresh Token으로 갱신 — 유출 시 피해를 최소화하는 설계입니다.

→ 다음 토픽에서 Passport.js + Google OAuth를 실제로 구현합니다.