[Daily morning study] OAuth 2.0와 OpenID Connect 인증 흐름

#daily morning study

Image


OAuth 2.0란

OAuth 2.0은 권한 위임(Authorization) 프로토콜이다. “구글로 로그인” 버튼을 눌렀을 때 구글이 내 비밀번호를 서드파티 앱에 넘기지 않고도 그 앱이 내 구글 리소스(이메일, 캘린더 등)에 접근할 수 있게 해주는 표준 방식이다.

핵심 키워드는 인증(Authentication)이 아닌 인가(Authorization) 이라는 점이다. “누구인지 확인”이 아니라 “무엇을 허용할지 결정”하는 프로토콜이다.


OAuth 2.0 주요 용어

용어설명
Resource Owner자원의 실제 주인 (= 사용자)
Client리소스에 접근하려는 앱 (= 서드파티 서버)
Authorization Server토큰을 발급하는 서버 (예: 구글 인증 서버)
Resource Server실제 데이터를 가진 서버 (예: 구글 API 서버)
Access Token리소스 접근 권한을 담은 토큰
Refresh TokenAccess Token 갱신에 쓰는 토큰
Scope허용된 권한 범위 (예: email, profile)

Authorization Code Flow (가장 안전한 방식)

웹 서버 앱처럼 백엔드가 있는 환경에서 권장하는 방식이다.

1. 사용자 → Client: "구글로 로그인" 클릭
2. Client → Authorization Server: 인증 요청
   GET /authorize?
     response_type=code
     &client_id=xxx
     &redirect_uri=https://myapp.com/callback
     &scope=email profile
     &state=random_string   ← CSRF 방지

3. Authorization Server → 사용자: 로그인 + 권한 동의 화면 표시
4. 사용자 동의 → Authorization Server: Authorization Code 발급
5. Authorization Server → Client (redirect_uri): code=abc123

6. Client → Authorization Server: 토큰 교환 (백채널, 서버 간 통신)
   POST /token
     code=abc123
     &grant_type=authorization_code
     &client_id=xxx
     &client_secret=yyy    ← 클라이언트 비밀키 포함
     &redirect_uri=https://myapp.com/callback

7. Authorization Server → Client: Access Token + Refresh Token
8. Client → Resource Server: Access Token으로 API 호출

Authorization Code를 중간에 교환하는 이유는, URL에 노출된 code만으로는 토큰을 발급받을 수 없고 client_secret까지 함께 있어야 하기 때문이다. 토큰 교환은 브라우저를 거치지 않는 백채널 통신으로 이루어진다.


PKCE (Proof Key for Code Exchange)

SPA나 모바일 앱처럼 client_secret을 안전하게 보관하기 어려운 환경에서 사용하는 확장이다.

1. Client: 랜덤 code_verifier 생성
2. Client: code_challenge = SHA256(code_verifier) 계산
3. Client → Authorization Server: 인증 요청에 code_challenge 포함
4. Authorization Server: code_challenge 임시 저장
5. Client → Authorization Server: 토큰 요청 시 code_verifier 전송
6. Authorization Server: SHA256(code_verifier) == code_challenge 검증

이렇게 하면 client_secret 없이도 인가 코드 탈취 공격을 막을 수 있다.


Access Token vs Refresh Token

항목Access TokenRefresh Token
목적리소스 접근Access Token 재발급
유효기간짧음 (수 분 ~ 1시간)길거나 없음
저장 위치메모리 (SPA) / 서버 세션HttpOnly 쿠키 / 서버 DB
노출 위험높음 (API 요청마다 전송)낮음 (재발급 시에만 사용)

Access Token 만료 시 매번 재로그인을 요구하면 UX가 나빠지므로, Refresh Token으로 새 Access Token을 조용히 발급받는 방식을 쓴다.


OpenID Connect (OIDC)

OAuth 2.0은 인가(Authorization)만 담당한다. “이 사용자가 누구인지(Authentication)” 표준화된 방식으로 확인하려면 OAuth 2.0 위에 얹힌 OpenID Connect 레이어가 필요하다.

OIDC는 OAuth 2.0 흐름에 ID Token을 추가한다.

POST /token 응답:
{
  "access_token": "eyJhb...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dGhpcyBp...",
  "id_token": "eyJhbGciOiJSUzI1NiJ9..."   ← OIDC 추가
}

ID Token은 JWT 형식이며, 페이로드에 사용자 정보가 담겨 있다:

{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",     사용자 고유 ID
  "aud": "client_id",
  "exp": 1311281970,
  "iat": 1311280970,
  "email": "user@example.com",
  "name": "홍길동"
}

OAuth 2.0 vs OIDC 정리

항목OAuth 2.0OpenID Connect
목적인가 (Authorization)인증 (Authentication)
토큰Access TokenAccess Token + ID Token
사용자 정보별도 API 호출 필요ID Token에 포함
기반독립 프로토콜OAuth 2.0 확장

“구글로 로그인” 구현 시 실제로는 OIDC를 사용한다. OAuth 2.0으로 리소스 접근 권한을 얻는 동시에 OIDC로 사용자 신원을 확인하는 것이다.


보안 고려사항

state 파라미터: CSRF 공격 방지. 요청 시 생성한 랜덤값을 콜백에서 검증한다.

redirect_uri 화이트리스트: Authorization Server에 미리 등록된 URI만 허용해야 한다. 임의의 URI로 code를 탈취하는 공격을 막기 위해서다.

토큰 저장 위치:

  • Access Token: SPA에서는 메모리(변수)에 저장. localStorage는 XSS에 취약하다.
  • Refresh Token: HttpOnly + Secure + SameSite 쿠키에 저장해 JS 접근을 차단한다.

토큰 검증: Resource Server는 Access Token의 서명, 만료, audience를 반드시 검증해야 한다.


언제 무엇을 쓸까

  • 내 서비스 사용자 로그인 (소셜 로그인): OIDC (+ OAuth 2.0)
  • 서드파티에게 내 데이터 접근 권한 부여: OAuth 2.0
  • 백엔드 있는 웹앱: Authorization Code Flow
  • SPA / 모바일앱: Authorization Code Flow + PKCE
  • 서버 간 통신 (사용자 없음): Client Credentials Flow