OAuth 2.0 개념 정리

Series: 인증과 인가

인증과 인가contains 5

들어가며

인증과 인가를 공부하다 보면 세션 기반 인증, JWT 기반 인증 다음으로 자연스럽게 마주치는 개념이 OAuth 2.0이다. 특히 구글 로그인, 카카오 로그인, 네이버 로그인과 같은 소셜 로그인 기능을 구현하려고 하면 OAuth 2.0이라는 용어가 거의 반드시 등장한다. 그래서 처음에는 OAuth 2.0을 단순히 소셜 로그인 기술인 줄 알았다.

정확히 말하자면 OAuth 2.0은 로그인 자체를 위한 기술이라기보다, 제3자 애플리케이션이 사용자의 리소스에 제한적으로 접근할 수 있도록 권한을 위임하는 인가 프레임워크이다. 예를 들어 어떤 서비스가 사용자의 구글 캘린더 정보를 가져오고 싶다고 해보자. 이때 사용자의 구글 아이디와 비밀번호를 직접 받아서 저장하는 것은 매우 위험하다. 사용자는 비밀번호를 외부 서비스에 넘겨야 하고, 외부 서비스는 사용자의 구글 계정 전체에 접근할 수 있게 된다.

OAuth 2.0은 이 문제를 해결하기 위해 등장했다. 사용자는 비밀번호를 외부 서비스에 넘기지 않고, 구글이 제공하는 동의 화면에서 '이 서비스가 내 캘린더를 읽는 것을 허용한다'라는 항목에 승인한다. 그러면 외부 서비스는 사용자의 비밀번호가 아니라, 제한된 권한을 가진 토큰을 받아서 필요한 리소스에만 접근한다.

이번 글에서는 OAuth 2.0이 왜 필요한지, 어떤 역할들이 등장하는지, 실제 흐름은 어떻게 동작하는지, 그리고 소셜 로그인과는 어떤 관계가 있는지 정리해보겠다.

1. OAuth 2.0이 필요한 이유

1.1. 비밀번호를 직접 공유하는 방식의 문제

OAuth 2.0을 이해하려면 먼저 OAuth가 없던 상황을 생각해보는 것이 좋다. 예를 들어 어떤 일정 관리 앱이 사용자의 구글 캘린더 정보를 가져오고 싶다고 해보자. OAuth 같은 권한 위임 방식이 없다면, 가장 단순한 방법은 사용자의 구글 아이디와 비밀번호를 직접 입력받는 것이다. 사용자가 일정 관리 앱에 구글 아이디와 비밀번호를 입력하면, 일정 관리 앱은 그 정보를 이용해서 구글에 로그인하고 캘린더 정보를 가져올 수 있다. 겉으로 보면 동작은 한다. 하지만 이 방식은 매우 위험하다.

사용자는 자신의 구글 계정 비밀번호를 외부 서비스에 넘겨야 한다. 외부 서비스는 캘린더만 필요한데도, 실제로는 구글 계정 전체에 접근할 수 있는 비밀번호를 가지게 된다. 또한 사용자가 나중에 이 서비스의 접근을 끊고 싶다면, 비밀번호를 바꾸는 것 말고는 명확한 방법이 없다. 서비스 제공자 입장에서도 부담이 크다. 사용자의 비밀번호를 직접 다뤄야 하므로 보안 책임이 커진다. 만약 이 서비스가 해킹당하면 사용자의 구글 계정 비밀번호가 유출될 수 있다.

OAuth 2.0은 이런 문제를 해결하기 위한 방식이다. OAuth 2.0에서는 사용자가 자신의 비밀번호를 제3자 서비스에 넘기지 않는다. 대신 사용자는 구글, 카카오, 네이버 같은 원래 서비스 제공자의 동의 화면에서 권한을 승인한다. 그러면 제3자 서비스는 비밀번호가 아니라 Access Token을 받는다. 이 Access Token은 사용자의 전체 계정 권한이 아니라, 승인된 범위 안에서만 사용할 수 있는 제한된 권한이다. 예를 들어 캘린더 읽기 권한만 승인했다면, 토큰으로 메일을 읽거나 비밀번호를 변경할 수는 없다.

1.2. 권한을 위임한다는 의미

OAuth 2.0의 핵심은 권한 위임이다. 여기서 권한 위임이란, 사용자가 자신의 계정 권한 전체를 넘기는 것이 아니라, 특정 서비스에게 특정 작업을 할 수 있는 제한된 권한만 맡기는 것을 의미한다. 예를 들어 사용자가 어떤 사진 인화 서비스에 구글 포토 접근 권한을 허용한다고 해보자. 이때 사용자는 자신의 구글 계정 전체를 넘기는 것이 아니다. '이 서비스가 내 사진 목록을 읽을 수 있다'라는 제한된 권한만 허용하는 것이다. 이 구조에서는 사용자가 언제든지 권한을 회수할 수 있다. 구글 계정 설정에서 해당 서비스의 접근 권한을 제거하면, 그 서비스는 더 이상 사용자의 리소스에 접근할 수 없다.

즉, OAuth 2.0은 다음과 같은 문제를 해결한다.

  • 사용자는 비밀번호를 공유하지 않아도 된다.
  • 제3자 서비스는 필요한 권한만 얻는다.
  • 권한 범위를 제한할 수 있다.
  • 사용자는 나중에 권한을 철회할 수 있다.

이러한 이유로 OAuth 2.0은 외부 서비스 간 연동에서 매우 중요한 표준처럼 사용된다.

2. OAuth 2.0의 주요 역할

OAuth 2.0에는 여러 역할이 등장한다. 처음 보면 용어가 낯설 수 있지만, 실제 상황에 대입하면 어렵지 않다. 예를 들어 사용자가 어떤 서비스에서 '구글 캘린더 연동하기' 버튼을 누른다고 가정해보자. 이때 사용자는 자신의 구글 캘린더 정보를 가지고 있는 사람이다. 연동을 요청하는 서비스는 사용자의 캘린더 정보를 사용하고 싶어하는 애플리케이션이다. 구글은 사용자의 인증을 처리하고, 권한 동의 화면을 보여주고, 토큰을 발급하는 역할을 한다. 그리고 실제 캘린더 데이터는 구글의 리소스 서버에 있다. 이 관계를 OAuth 2.0 용어로 표현하면 다음과 같다.

2.1. Resource Owner

Resource Owner는 리소스의 소유자이다. 대부분의 경우 사용자를 의미한다. 예를 들어 구글 캘린더 연동에서는 구글 캘린더 데이터를 소유한 사용자가 Resource Owner이다. 사용자는 자신의 데이터를 어떤 서비스가 사용할 수 있는지 승인하거나 거부할 수 있다. OAuth 2.0에서 권한 위임의 출발점은 Resource Owner의 동의이다. 사용자가 동의하지 않으면 제3자 서비스는 사용자의 리소스에 접근할 수 없다.

2.2. Client

Client는 사용자의 리소스에 접근하려는 애플리케이션이다. 예를 들어 사용자의 구글 캘린더 정보를 가져오려는 일정 관리 앱이 Client이다. 여기서 Client라는 말은 프론트엔드 클라이언트만 의미하는 것이 아니라, OAuth 흐름에서 권한을 위임받으려는 애플리케이션 전체를 의미한다. Client는 사용자를 Authorization Server로 이동시켜 권한 동의를 받는다. 그리고 동의가 완료되면 Access Token을 발급받아 Resource Server에 요청을 보낸다.

2.3. Authorization Server

Authorization Server는 사용자를 인증하고, 권한 동의를 처리하며, 토큰을 발급하는 서버이다. 구글 로그인을 예로 들면, 구글의 인증 서버가 Authorization Server 역할을 한다. 사용자는 이 서버에서 로그인하고, '이 애플리케이션이 내 정보에 접근해도 되는가?'라는 동의 화면을 확인한다. 동의가 완료되면 Authorization Server는 Client에게 Authorization Code를 발급하거나, 최종적으로 Access Token을 발급한다.

2.4. Resource Server

Resource Server는 실제 보호된 리소스를 가지고 있는 서버이다. 구글 캘린더 API 서버, 구글 드라이브 API 서버, 카카오 사용자 정보 API 서버 등이 Resource Server에 해당할 수 있다. Client는 Authorization Server에서 발급받은 Access Token을 Resource Server에 전달하고, Resource Server는 토큰을 확인한 뒤 요청한 리소스를 응답한다.

2.5. 네 역할의 관계

전체 관계를 그림으로 보면 다음과 같다.

flowchart LR
    User[Resource Owner<br/>사용자] --> Client[Client<br/>제3자 애플리케이션]
    Client --> AuthServer[Authorization Server<br/>인증/인가 및 토큰 발급]
    Client --> ResourceServer[Resource Server<br/>보호된 리소스 제공]
    AuthServer --> ResourceServer

이 그림에서 핵심은 Client가 사용자의 비밀번호를 직접 받지 않는다는 것이다. 사용자는 Authorization Server에서 직접 로그인하고 동의한다. Client는 그 결과로 토큰을 받아서 Resource Server에 접근한다.

3. OAuth 2.0의 기본 흐름

OAuth 2.0에는 여러 가지 흐름이 있지만, 웹 서비스에서 가장 많이 사용하는 방식은 Authorization Code Grant이다. 특히 서버가 있는 웹 애플리케이션에서는 Authorization Code Grant가 가장 일반적이다. 최근에는 보안을 강화하기 위해 여기에 PKCE(Proof Key for Code Exchange, 픽시)를 함께 사용하는 방식이 권장된다. 먼저 기본적인 Authorization Code 흐름을 살펴보자.

3.1. Authorization Code Grant 흐름

사용자가 Client 서비스에서 '구글로 로그인' 또는 '구글 캘린더 연동하기' 버튼을 클릭한다고 해보자. Client는 사용자를 구글의 Authorization Server로 이동시킨다. 이때 요청에는 Client를 식별하는 값, 요청하는 권한 범위, 로그인 완료 후 돌아올 주소 등이 포함된다. 사용자는 구글 로그인 화면에서 로그인하고, 권한 동의 화면을 확인한다. 예를 들어 '이 애플리케이션이 내 기본 프로필 정보를 볼 수 있습니다' 또는 '이 애플리케이션이 내 캘린더를 읽을 수 있습니다' 같은 화면이 표시된다.

Authorization Server는 사용자가 동의하면 해당 사용자를 Client가 미리 등록해둔 redirect URI로 다시 돌려보낸다. 이때 URL에는 Authorization Code라는 임시 코드가 포함된다. Client의 서버는 이 Authorization Code를 Authorization Server에 다시 전달한다. 그리고 이 코드가 유효하면 Authorization Server는 Access Token을 발급한다. 이 Access Token을 사용하면 Client는 Resource Server에 사용자의 리소스를 요청할 수 있다.

sequenceDiagram
    participant User as 사용자(Resource Owner)
    participant Client as Client 애플리케이션
    participant Auth as Authorization Server
    participant Resource as Resource Server

    User->>Client: 소셜 로그인 또는 연동 버튼 클릭
    Client->>Auth: 권한 요청<br/>client_id, redirect_uri, scope, state
    Auth->>User: 로그인 및 권한 동의 화면 표시
    User->>Auth: 로그인 및 동의
    Auth-->>Client: redirect_uri로 이동<br/>Authorization Code 전달
    Client->>Auth: Authorization Code로 토큰 요청
    Auth-->>Client: Access Token 발급
    Client->>Resource: Access Token으로 리소스 요청
    Resource-->>Client: 보호된 리소스 응답

여기서 Authorization Code는 Access Token이 아니다. Authorization Code는 Access Token을 발급받기 위한 임시 코드이다. 이 코드는 짧은 시간 동안만 유효하고, 한 번 사용되면 다시 사용할 수 없다. 이렇게 한 단계를 더 거치는 이유는 Access Token이 브라우저 URL에 직접 노출되는 것을 피하기 위해서이다. 사용자의 브라우저를 통해 직접 Access Token을 전달하면 히스토리, 로그, 리퍼러 등을 통해 노출될 위험이 있다. 그래서 브라우저에는 Authorization Code만 전달하고, 실제 Access Token 교환은 서버 간 통신으로 처리한다.

4. Scope

OAuth 2.0에서 중요한 개념 중 하나가 scope이다. scope는 Client가 요청하는 권한의 범위를 의미한다. 예를 들어 사용자 프로필만 필요한 서비스가 있다면 프로필 조회 권한만 요청하면 된다. 캘린더를 읽어야 하는 서비스라면 캘린더 읽기 권한을 요청한다. scope가 중요한 이유는 OAuth 2.0의 목적이 필요한 권한만 위임받는 것이기 때문이다. 예를 들어 어떤 서비스가 단순히 소셜 로그인을 위해 사용자의 이메일만 필요하다고 해보자. 이 경우에는 이메일 조회 권한 정도만 요청하면 된다. 그런데 이 서비스가 캘린더 수정 권한, 드라이브 파일 삭제 권한까지 요청한다면 사용자는 불필요하게 큰 권한을 넘겨주게 된다. OAuth 2.0에서는 이렇게 필요한 권한을 scope로 제한한다. Authorization Server는 사용자에게 이 애플리케이션이 어떤 권한을 요청하는지 보여주고, 사용자는 이를 승인하거나 거부한다.

flowchart TB
    Client[Client] --> Request[권한 요청]
    Request --> Scope1[profile<br/>기본 프로필 조회]
    Request --> Scope2[email<br/>이메일 조회]
    Request --> Scope3[calendar.read<br/>캘린더 읽기]
    Request --> Scope4[calendar.write<br/>캘린더 수정]

scope를 설계할 때는 최소 권한 원칙을 지키는 것이 중요하다. 서비스가 실제로 필요로 하는 권한만 요청해야 한다. 그래야 사용자의 신뢰를 얻을 수 있고, 토큰이 탈취되었을 때 피해 범위도 줄일 수 있다.

5. Access Token과 Refresh Token

OAuth 2.0에서도 Access Token과 Refresh Token이 등장한다. 앞서 JWT 기반 인증에서 다뤘던 개념과 역할은 비슷하다. Access Token은 Resource Server에 접근하기 위해 사용하는 토큰이다. Client는 Access Token을 가지고 Resource Server에 요청을 보낸다. Resource Server는 이 토큰을 확인하고, 요청된 리소스에 접근할 권한이 있는지 판단한다. Refresh Token은 Access Token이 만료되었을 때 새로운 Access Token을 발급받기 위해 사용된다. Access Token은 보통 만료 시간이 짧기 때문에, 사용자가 매번 다시 로그인하지 않도록 Refresh Token을 함께 사용할 수 있다.

다만 OAuth 2.0에서 중요한 점은, Access Token이 반드시 JWT일 필요는 없다는 것이다. 어떤 서비스는 Access Token을 JWT로 발급할 수도 있고, 어떤 서비스는 서버에서만 의미를 알 수 있는 랜덤 문자열 형태로 발급할 수도 있다. 즉, OAuth 2.0은 토큰의 구체적인 형식을 강제하는 기술이 아니다. OAuth 2.0은 '어떤 절차로 권한을 위임하고 토큰을 발급할 것인가?'에 대한 프레임워크이고, 토큰이 JWT인지 opaque token인지는 구현에 따라 달라질 수 있다.

flowchart LR
    Token[OAuth 2.0 Token] --> JWT[JWT 형태<br/>토큰 자체에 정보 포함]
    Token --> Opaque[Opaque Token 형태<br/>서버가 저장소에서 조회해야 의미 확인]

JWT 형태의 Access Token은 Resource Server가 토큰 자체를 검증해서 사용자와 권한 정보를 확인할 수 있다. 반면 opaque token은 토큰 문자열 자체만 봐서는 의미를 알 수 없다. Resource Server가 Authorization Server나 토큰 저장소에 문의해야 한다. 각 방식에는 장단점이 있다. JWT는 서버 간 검증이 간편하고 확장성이 좋지만, 한 번 발급된 토큰을 즉시 무효화하기 어렵다. opaque token은 서버가 상태를 관리해야 하지만, 토큰을 즉시 무효화하거나 세밀하게 제어하기 쉽다.

6. OAuth 2.0과 소셜 로그인

나도 그랬듯이 많은 사람들이 OAuth 2.0을 소셜 로그인이라고 이해한다. 실제로 소셜 로그인 구현에서 OAuth 2.0 흐름을 많이 사용하기 때문에 완전히 틀린 말은 아니다. 하지만 엄밀히 말하면 OAuth 2.0은 인가를 위한 프레임워크이다. 즉, '이 애플리케이션이 사용자의 리소스에 접근해도 되는가?'를 다루는 기술이다. 반면 로그인은 인증의 문제이다. 로그인에서 중요한 질문은 '이 사용자가 누구인가?'이다. 여기서 혼동이 생긴다. OAuth 2.0으로 구글에서 Access Token을 받아오고, 그 토큰으로 사용자 프로필 API를 호출하면 사용자의 이메일이나 식별자를 알 수 있다. 이 정보를 바탕으로 우리 서비스에 회원가입 또는 로그인을 처리할 수 있다.

이런 방식으로 소셜 로그인을 구현하기 때문에 OAuth 2.0이 로그인 기술처럼 보이는 것이다. 하지만 OAuth 2.0 자체만으로는 사용자가 누구인지를 표준화해서 알려주는 프로토콜이 아니다. 이 문제를 해결하기 위해 등장한 것이 OpenID Connect(OIDC)이다. OIDC는 OAuth 2.0 위에 인증 계층을 추가한 프로토콜이다. OIDC에서는 ID Token이라는 토큰이 등장한다. ID Token은 사용자가 누구인지에 대한 정보를 담고 있으며, 소셜 로그인에서는 이 ID Token을 통해 사용자 인증을 처리할 수 있다.

flowchart TB
    OAuth[OAuth 2.0<br/>인가 프레임워크] --> Access[Access Token<br/>리소스 접근 권한]
    OIDC[OpenID Connect<br/>인증 계층] --> IDToken[ID Token<br/>사용자 신원 정보]
    OIDC --> OAuth

정리하면, OAuth 2.0은 원래 권한 위임을 위한 기술이고, 소셜 로그인은 이 흐름을 이용해서 사용자 정보를 가져와 로그인에 활용하는 방식이다. 더 정확한 인증 표준은 OAuth 2.0 위에 만들어진 OpenID Connect라고 볼 수 있다.

7. Authorization Code와 PKCE

OAuth 2.0 흐름을 보면 PKCE도 함께 언급되는 경우가 많다. PKCE는 Proof Key for Code Exchange의 약자이다. Authorization Code 흐름에서 Authorization Code가 탈취되더라도, 공격자가 이를 이용해 Access Token을 발급받지 못하도록 보완하는 방식이다. 기본 Authorization Code 흐름에서는 사용자가 동의한 뒤 Client가 Authorization Code를 받는다. 그리고 Client는 이 code를 이용해 Access Token을 요청한다. 그런데 만약 이 code가 중간에 탈취된다면 공격자가 대신 토큰을 발급받을 수 있는 위험이 있다. PKCE는 이 문제를 막기 위해 code_verifiercode_challenge라는 값을 사용한다.

먼저 Client는 랜덤한 문자열인 code_verifier를 만든다. 그리고 이 값을 해시해서 code_challenge를 만든다. 권한 요청을 보낼 때는 code_challenge만 Authorization Server에 보낸다. 나중에 Authorization Code를 Access Token으로 교환할 때, Client는 처음에 만든 code_verifier를 함께 보낸다. Authorization Server는 이 code_verifier를 해시해서 처음에 받은 code_challenge와 일치하는지 확인한다. 일치하면 정상적인 Client가 보낸 요청으로 판단하고 Access Token을 발급한다. 일치하지 않으면 토큰 발급을 거부한다.

sequenceDiagram
    participant Client as Client
    participant Auth as Authorization Server

    Client->>Client: code_verifier 생성
    Client->>Client: code_verifier로 code_challenge 생성

    Client->>Auth: 권한 요청<br/>code_challenge 포함
    Auth-->>Client: Authorization Code 발급

    Client->>Auth: 토큰 요청<br/>Authorization Code + code_verifier
    Auth->>Auth: code_verifier로 code_challenge 재계산
    Auth->>Auth: 기존 code_challenge와 비교
    Auth-->>Client: Access Token 발급

PKCE의 핵심은 Authorization Code만 훔쳐서는 Access Token을 발급받을 수 없게 만드는 것이다. 공격자가 Authorization Code를 탈취하더라도, 처음에 생성된 code_verifier를 알지 못하면 토큰 교환에 실패한다. 예전에는 PKCE가 모바일 앱이나 SPA처럼 client_secret을 안전하게 보관하기 어려운 public client에서 주로 사용되었다. 하지만 최근에는 보안을 강화하기 위해 일반적인 Authorization Code 흐름에서도 함께 사용하는 경우가 많다.

8. OAuth 2.0의 전체 흐름 예시

이제 실제 소셜 로그인 흐름을 예로 들어 OAuth 2.0이 어떻게 동작하는지 정리해보자. 사용자가 우리 서비스에서 '구글로 로그인' 버튼을 클릭한다. 우리 서비스는 사용자를 구글의 Authorization Server로 이동시킨다. 이때 우리 서비스는 client_id, redirect_uri, scope, state, code_challenge 같은 값을 함께 보낸다.

사용자는 구글 로그인 화면에서 로그인하고, 우리 서비스가 요청한 권한을 확인한다. 사용자가 동의하면 구글은 사용자를 우리 서비스의 redirect_uri로 다시 이동시킨다. 이때 Authorization Code가 함께 전달된다. 우리 서비스의 서버는 이 Authorization Code와 code_verifier를 구글 Authorization Server에 전달한다. 구글은 code_verifier가 올바른지 확인하고, 문제가 없다면 Access Token을 발급한다.

우리 서비스는 이 Access Token을 이용해 구글의 사용자 정보 API를 호출한다. 여기서 사용자의 고유 식별자나 이메일을 얻을 수 있다. 이후 우리 서비스는 이 정보를 바탕으로 기존 회원인지 확인한다. 기존 회원이면 로그인 처리하고, 처음 방문한 사용자라면 회원가입을 진행할 수 있다.

마지막으로 우리 서비스는 자체 세션이나 자체 JWT를 발급하여 사용자를 로그인 상태로 유지한다.

sequenceDiagram
    participant User as 사용자
    participant App as 우리 서비스
    participant GoogleAuth as 구글 Authorization Server
    participant GoogleAPI as 구글 Resource Server
    participant DB as 우리 서비스 DB

    User->>App: 구글로 로그인 클릭
    App->>GoogleAuth: 권한 요청<br/>client_id, redirect_uri, scope, state, code_challenge
    GoogleAuth->>User: 로그인 및 동의 화면
    User->>GoogleAuth: 동의
    GoogleAuth-->>App: Authorization Code 전달

    App->>GoogleAuth: Authorization Code + code_verifier로 토큰 요청
    GoogleAuth-->>App: Access Token 발급

    App->>GoogleAPI: Access Token으로 사용자 정보 요청
    GoogleAPI-->>App: 사용자 프로필 정보 응답

    App->>DB: 기존 회원 조회 또는 생성
    DB-->>App: 사용자 정보 반환

    App-->>User: 우리 서비스 자체 로그인 처리<br/>세션 또는 JWT 발급

여기서 중요한 점은 구글이 발급한 Access Token을 우리 서비스의 인증 토큰처럼 그대로 쓰지 않는다는 것이다. 보통은 구글에서 사용자 정보를 가져온 뒤, 우리 서비스의 사용자 계정과 매핑하고, 이후에는 우리 서비스가 자체적으로 세션이나 JWT를 발급한다. 즉, 소셜 로그인에서 OAuth 2.0은 외부 제공자를 통해 사용자를 확인하고 필요한 정보를 가져오는 과정에 사용되고, 실제 우리 서비스 로그인 상태 유지는 별도의 인증 방식으로 처리하는 경우가 많다.

9. State 파라미터

OAuth 2.0 흐름에서 자주 등장하는 값 중 하나가 state이다. state는 Client가 Authorization Server로 권한 요청을 보낼 때 함께 보내는 임의의 값이다. Authorization Server는 사용자가 로그인과 동의를 완료한 뒤 redirect_uri로 돌아올 때 이 state 값을 그대로 돌려준다. 이 값을 사용하는 이유는 CSRF 공격을 방지하기 위해서이다. 예를 들어 공격자가 사용자의 브라우저를 이용해 의도하지 않은 OAuth 요청을 시작하게 만들 수 있다고 해보자. 이 경우 사용자가 원하지 않는 계정으로 연결되거나, 공격자가 만든 흐름에 사용자가 휘말릴 수 있다.

Client는 OAuth 요청을 시작할 때 예측하기 어려운 state 값을 생성하고, 이를 세션이나 쿠키에 저장해둔다. 이후 redirect_uri로 돌아온 요청에 포함된 state 값이 처음 저장한 값과 일치하는지 확인한다. 값이 일치하면 정상적으로 우리 서비스가 시작한 OAuth 흐름이라고 판단한다. 값이 다르면 요청을 거부한다.

sequenceDiagram
    participant App as Client
    participant Auth as Authorization Server

    App->>App: 랜덤 state 생성 및 저장
    App->>Auth: 권한 요청<br/>state 포함
    Auth-->>App: redirect_uri로 이동<br/>code + state 반환
    App->>App: 저장된 state와 반환된 state 비교
    App->>App: 일치하면 정상 처리<br/>불일치하면 요청 거부

state는 단순한 부가 값처럼 보이지만, OAuth 흐름의 보안에서 매우 중요한 역할을 한다. 따라서 실제 구현에서는 반드시 예측하기 어려운 랜덤 값을 사용하고, redirect 이후 검증해야 한다.

10. OAuth 2.0 사용 시 주의할 점

OAuth 2.0은 강력한 권한 위임 구조를 제공하지만, 잘못 구현하면 보안 문제가 생길 수 있다.

  1. redirect_uri 검증이 중요하다. Authorization Server는 토큰이나 Authorization Code를 미리 등록된 redirect_uri로만 보내야 한다. 만약 redirect_uri 검증이 느슨하면 공격자가 자신의 서버로 Authorization Code를 전달받을 수 있다.
  2. client_secret 관리가 중요하다. 서버 기반 애플리케이션에서는 client_secret을 안전하게 서버에 보관할 수 있지만, SPA나 모바일 앱처럼 사용자가 설치하거나 브라우저에서 실행되는 환경에서는 client_secret을 안전하게 숨길 수 없다. 이런 환경에서는 PKCE를 사용하는 것이 중요하다.
  3. scope를 최소화해야 한다. 필요한 권한만 요청해야 한다. 사용자 이메일만 필요한데 캘린더 수정 권한이나 드라이브 접근 권한까지 요청하는 것은 좋지 않다. 권한이 커질수록 토큰이 탈취되었을 때의 피해도 커진다.
  4. Access Token을 클라이언트에 노출하지 않도록 주의해야 한다. 특히 Authorization Code 흐름에서는 Access Token을 브라우저 URL에 직접 노출하지 않고, 서버 간 통신으로 교환하는 것이 중요하다.
  5. OAuth 2.0과 로그인 처리를 혼동하지 않아야 한다. OAuth 2.0은 권한 위임을 위한 프레임워크이고, 사용자 인증 정보를 표준적으로 다루려면 OpenID Connect까지 함께 이해하는 것이 좋다.

11. 사용 기준

OAuth 2.0은 외부 서비스의 리소스에 접근해야 할 때 적합하다. 예를 들어 사용자의 구글 캘린더를 읽어야 하거나, 카카오 사용자 정보를 가져와야 하거나, 깃허브 저장소 목록을 조회해야 한다면 OAuth 2.0 흐름을 사용하는 것이 자연스럽다. 또한 소셜 로그인을 구현할 때도 OAuth 2.0 또는 OpenID Connect 흐름을 사용하게 된다. 사용자는 별도의 회원가입 없이 기존 계정을 이용해 로그인할 수 있고, 서비스는 외부 제공자로부터 사용자 식별 정보를 받아 자체 계정과 연결할 수 있다. 반면 단순히 우리 서비스 내부 사용자만 로그인시키는 경우라면 OAuth 2.0이 반드시 필요한 것은 아니다. 이 경우에는 세션 기반 인증이나 JWT 기반 인증만으로도 충분하다. 즉, OAuth 2.0은 우리 서비스 안에서 로그인 상태를 유지하는 방법이라기보다, 외부 서비스로부터 권한을 위임받고 리소스에 접근하는 방법에 가깝다.

마치며

OAuth 2.0은 제3자 애플리케이션이 사용자의 비밀번호를 직접 받지 않고도, 사용자의 리소스에 제한적으로 접근할 수 있도록 만들어주는 인가 프레임워크이다. 핵심은 권한 위임이다. 사용자는 자신의 비밀번호를 넘기지 않고, 필요한 범위의 권한만 승인한다. Client는 그 결과로 Access Token을 발급받고, Resource Server에 접근한다. OAuth 2.0을 이해할 때는 단순히 소셜 로그인으로만 보면 부족하다. OAuth 2.0은 원래 인가를 위한 기술이고, 소셜 로그인은 이 흐름을 활용한 대표적인 사용 사례이다. 사용자 인증까지 표준적으로 다루려면 OpenID Connect도 함께 이해하는 것이 좋다. 결국 OAuth 2.0의 핵심은 누군가에게 내 비밀번호를 넘기지 않고도, 필요한 권한만 안전하게 위임하는 것이라고 볼 수 있다.