많은 사람들이
OAuth == 소셜 로그인
이라고 알고 있다. 반은 맞고 반은 틀린 것인데, OAuth는 인가를 위한 프로토콜인데 로그인과 같은 인증은 하지 못한다. 이를 위해 인증 방식이 추가되었는데 이를 한번 알아보자!
위에서도 말했지만 OAuth는 권한 부여(인가)를 위한 프로토콜이다.
하지만 실제 서비스에서는 권한을 가진 것이 아닌 실제로 사용자가 내 서비스의 회원인지 확인하는 인증 기능도 필요했다.
OAuth 2.0은 리소스에 대한 접근 권한 여부만 다루고, OIDC는 누가 로그인했는지를 검증하고 추가 리소스를 제공한다.
즉 OIDC는 OAuth 2.0 위에 인증을 하기 위한 프로토콜이다.
OIDC 인증은 사용자가 한 애플리케이션에 로그인하고 다른 애플리케이션에 대한 액세스 권한을 받을 수 있도록 허용하는 방식으로 작동한다.
예를 들어 사용자가 뉴스 사이트에서 계정을 만들려는 경우 새 계정을 만드는 대신 Facebook을 사용하여 계정을 만드는 옵션이 있을 수 있다. Facebook을 선택하는 경우 OIDC 인증을 사용한다.
OpenID 공급자라고 하는 Facebook은 인증 프로세스를 처리하고 사용자 프로필과 같은 특정 정보를 신뢰 당사자인 뉴스 사이트에 제공하기 위해 사용자의 동의를 얻는다.
사용자가 액세스하려는 애플리케이션(신뢰 당사자)으로 이동합니다.
사용자가 사용자 이름 및 암호를 입력합니다.
신뢰 당사자가 OpenID 공급자에게 요청을 보냅니다.
OpenID 공급자는 사용자의 자격 증명의 유효성을 검사하고 권한 부여를 얻습니다.
OpenID 공급자는 ID 토큰과 종종 액세스 토큰을 신뢰 당사자에게 보냅니다.
신뢰 당사자는 사용자 디바이스에 액세스 토큰을 보냅니다.
액세스 토큰 및 신뢰 당사자에 제공된 정보에 따라 사용자에게 액세스 권한이 부여됩니다.
기본적으로 OIDC는 OAuth 2.0의 Authorization Code Flow
를 따르면서, 추가적인 ID Token을 발급받는다.
Resource Owner(사용자)
→ 인증 요청 → Authorization Server
Authorization Server
→ 인증 완료 후 Authorization Code 발급 → Client
Client
→ Authorization Code로 토큰 요청 → Authorization Server
Authorization Server
→ Access Token + ID Token 발급 → Client
Client
→ (필요 시) Access Token으로 UserInfo Endpoint에 사용자 정보 요청
iss
: 발급자 (Issuer)sub
: 사용자 고유 ID (Subject)aud
: 발급 대상 클라이언트 ID (Audience)exp
: 만료 시각 (Expiration)iat
: 발급 시각 (Issued At)nonce
: 재사용 공격 방지를 위한 랜덤 값email
, name
, picture
등 프로필 정보OIDC에서는 Access Token을 사용해서 추가 사용자 정보를 얻을 수 있는 표준 API인 UserInfo EndPoint를 제공한다.
GET /userinfo HTTP/1.1
Authorization: Bearer {access_token}
{
"sub": "1234567890",
"name": "홍길동",
"email": "gildong@example.com",
"picture": "https://example.com/profile.png"
}
요즘 구글, 네이버, 카카오, 애플 로그인 같은 소셜 로그인은 거의 모두 OIDC를 기반으로 한다.
OAuth 2.0은 권한 부여(API 접근 권한 부여)에 적합하지만
소셜 로그인은 "이 사용자가 누구인가"를 알아야 하니까
"ID Token"과 "UserInfo"로 인증까지 확실히 할 수 있는 OIDC가 필요하다.
그래서 요즘 소셜 로그인은 사실상
"Authorization Code Flow + OpenID Connect" 조합으로 작동한다.
또한 React로 만들어진 백엔드 없는 SPA 웹이나 모바일 앱 같은 경우는 "Authorization Code Flow + PKCE + OpenID Connect"를 사용한다.
위의 경우 클라이언트 시크릿을 안전하게 보관할 수 없기 때문에 Proof Key를 활용하여 Authorization을 안전하게 발급받기 위함이다.
다만 아직 표준화는 안되었지만 OAuth 2.1
스펙에서는 모든 클라이언트를 대상으로 Authrization + PKCE가 필수이다. 따라서 보안 문제로 시끄러운 요즘 OIDC를 구현할 때 고려하는 것도 좋아보인다.
Authorization Code + PKCE를 잘 모른다면 OAuth 2.0~2.1
사용자 → 인증 요청 → 인증 서버
code_challenge
code_verifier
를 만든다.code_verifier
를 해싱한 값이 code_challenge
이다.인증 서버 → 인증 완료 후 Authorization Code 발급 → 클라이언트
클라이언트 → Authorization Code로 토큰 요청 → 인증 서버
인증 서버 → Access Token + ID Token 발급 → 클라이언트
클라이언트 → (필요 시) Access Token으로 UserInfo 요청 → 리소스 서버
클라이언트는 ID Token을 수신한 후 다음 항목들을 검증해야 한다:
ID Token은 JWT(Json Web Token) 형식이고, 서버에서 서명(JWS) 되어 발급된다.
클라이언트는 인증 서버(OP)의 공개 키를 가져와서
→ 이 ID Token의 서명이 올바른지 검증해야 한다.
iss는 토큰을 발급한 인증 서버(OP)의 고유 식별자(보통 URL)다.
클라이언트는 받은 ID Token 안의 iss 값이
→ 내가 신뢰하는 인증 서버의 값과 정확히 일치하는지 확인해야 한다.
aud는 "이 토큰이 누구를 위해 발급되었는가"를 나타낸다.
보통 클라이언트의 client_id가 aud에 들어간다.
클라이언트는 ID Token 안의 aud 배열에
→ 자신의 client_id가 포함되어 있는지 확인해야 한다.
exp는 토큰의 만료 시각이다.
클라이언트는 현재 시간이 exp 시간보다 이전인지 확인해야 한다.
만약 현재 시각이 exp 이후라면,
→ 이 토큰은 만료된 것이므로 거부해야 한다.
인증 요청을 보낼 때 클라이언트는 랜덤한 nonce 값을 함께 보냈다.
이 nonce는 Replay Attack(재사용 공격) 방지를 위한 값이다.
ID Token 안에도 이 nonce 값이 들어있는데,
클라이언트는 "내가 보낸 nonce"와 "ID Token에 들어있는 nonce"가 일치하는지 확인해야 한다.