서비스 개발을 하다 보면 소셜 로그인 기능을 한 번쯤 구현해 보게 되는데요. 요즘에는 대부분의 서비스가 제공하는 것 같습니다.
이번 포스팅에서는 소셜 로그인의 근간이 되는 기술인 OAuth의 스펙에 대해 살펴보고, 직접 구현해 보며 OAuth에 대해 조금 더 이해해 보고자 합니다.
OAuth란 무엇일까요? OAuth는 RFC 6749에 정의된 표준으로 인터넷 사용자들이 다른 웹사이트를 접속할 때 비밀번호를 제공하지 않고 자신들의 정보에 대해 웹사이트에게 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는 개방형 표준 프로토콜입니다.
말이 좀 어려운데요. 쉽게 말하면 이용하려는 사이트에 로그인하기 위해 직접 비밀번호를 제공하는 대신, 특정 플랫폼의 계정(ex: 구글, 페이스북, 카카오)을 가지고 해당 서비스에 로그인하는 것을 말합니다. 요즘은 대부분의 서비스에서 제공하기 때문에 아래와 같은 소셜 로그인 화면을 쉽게 접할 수 있습니다.
그럼 소셜로그인은 어떤 과정을 통해 이루어질까요?
OAuth에 대한 RFC 문서를 살펴보면 OAuth의 진행 플로우를 볼 수 있습니다.
(A)~(F)까지의 과정을 거쳐 유저는 로그인하게 되는데요. 각각의 과정에 대해 살펴보기 전에, 로그인 과정에서의 참여자와 그 역할에 대해 먼저 간단히 살펴보겠습니다.
문서에 따르면 OAuth 로그인 플로우 동작에 관여하는 참여자는 크게 네 가지로 구분됩니다.
- Client : 카카오 인증 서버에 요청을 보내는 주체입니다.
- Resource Owner: 정보의 주인을 의미합니다.
- Authorization Server: 인증을 관리하는 서버입니다. 이 곳에서 access token을 발급합니다.
- Resource Server : 실제 유저 정보를 가지고 있는 서버입니다.
실제 서버 구성 환경에 따라 인증 서버와 자원 서버는 분리되어 있지 않은 경우도 있기 때문에 Auth 서버와 Resource 서버를 하나로 합쳐서 설명하기도 합니다.
그럼 실제 로그인 상황에서 각 참여자는 누가 될 수 있을까요? 이해를 돕기 위해 유저가 Velog 서비스에서 카카오 로그인을 하는 상황이라고 가정하고 대입해보겠습니다.
- Client : Velog 서비스
- Resource Owner : 로그인하려는 유저
- Authorization Server : 카카오 인증 서버
- Resource Server : 카카오 api 서버
좀 더 이해가 되셨나요?
그럼 바로 이어서 OAuth flow의 (A)부터 (F)까지의 과정을 동일하게 카카오 로그인으로 대입해보겠습니다.
- (A) : 클라이언트가 유저에게 로그인 요청(로그인 버튼 클릭과 같은 액션시 발생)
- (B) : 유저가 카카오 로그인 진행
- (C)~(D) : 카카오 서버가 Access Token 발급
- (E) : 카카오 서버에게 추가 유저 정보 요청(닉네임, 프로필 이미지 등)
- (F) : 카카오 서버가 유저의 정보 응답
이런 과정을 거쳐 유저는 로그인하게 됩니다.
지금까지 OAuth를 이용한 로그인 플로우를 살펴보았습니다. 이제 카카오 로그인을 직접 구현해보며 OAuth 플로우와 비교해보겠습니다.
Kakao Developers를 보면 로그인을 할 수 있는 절차에 대해 3가지 Step으로 나누어 자세하게 설명하고 있습니다.
첫번째 단계는 유저가 로그인을 하고, Access Token을 받기 위한 인가 코드를 가져오는 과정입니다. 위의 OAuth flow에서는 (A)~(B) 단계가 됩니다.
위 이미지를 보면 앞서 OAuth Flow에서는 볼 수 없었던 Service Client와 Service Server가 등장하는데요.
이들은 쉽게 말해 서비스의 프론트와 백엔드라고 이해하시면 됩니다. 앞서 살펴본 OAuth flow에서는 Client로 통합되어 표현되어 있는 부분입니다.
구현에는 Service Client로 브라우저, Service Server로 Nextjs 13의 Route Handler(기존의 API route)를 이용해서 진행 하겠습니다.
우선 간단하게 로그인 페이지를 할 수 있는 페이지를 만들어 볼텐데요. 버튼을 누르면 카카오 로그인 페이지로 이동하는 간단한 페이지입니다.
실제 이동 경로는 브라우저 -> next server -> 카카오 로그인 페이지로 이동하게 됩니다. 브라우저에서 인가 코드를 요청하지 않고 웹 서버를 거치는 이유는 인가 코드를 브라우저에 노출하지 않기 위함입니다.
아래는 홈 컴포넌트의 코드입니다.
로그인 버튼 클릭시 백엔드의 /api/oauth/kakao
경로로 로그인을 요청하고, 이동한 경로에서는 카카오 로그인 페이지로 redirect시켜줍니다. (라인 7~9)
이때 인가 code를 받을 수 있는 route 경로는 /oauth/code
로 해줍니다. 유저가 카카오 로그인을 정상적으로 마치면 해당 경로로 access token을 얻을 수 있는 인가 코드를 넣어줍니다.
아래는 /api/oauth/kakao
route handler 코드입니다.
redirect후 유저는 로그인 페이지로 이동하게 되고, 아래와 같은 로그인 화면을 보게 됩니다.
약관 동의 페이지를 거쳐 가입을 하게되면, 앞서 입력한 Redirect_URI(/oauth/code
)로 카카오에서 redirect를 시켜줍니다.
아래는 /oauth/code
route handler의 일부입니다.
url에서 인가 code를 받아줍니다. 이렇게 Step 1의 단계를 마쳤습니다.
두번째 단계는 code를 통해 access token을 얻어오는 단계인데요. OAuth flow에서는 (C)~(D) 과정입니다.
Step1에서 /oauth/code
핸들러에서 code를 받은 뒤부터 이어서 진행하겠습니다. 인가코드를 받아 왔으니 카카오 인증 서버에 접근해서 access token을 받아와줍시다. (라인5~6)
getKakaoToken 함수 구현부를 살펴봅시다.
특별한 건 없는데요. 인가 코드를 카카오 서버에서 정해준 스펙대로 보내주면 access token을 받아올 수 있습니다.
API 통신시 body를 Json.Stringify로 말아서 보내면 400에러를 던져줘서 찾아보니 이런 이슈가 있더라구요. (라인 12~24) 여기를 참고해서 해결했습니다.
마지막 단계는 사용자 정보를 가져와서 로그인 처리를 완료하는 단계입니다. 앞 단계에서 카카오 인증 서버로부터 access token을 받아 왔는데요. 이 토큰을 이용해서 유저의 닉네임과 같은 개인 정보를 가져올 수 있습니다.
유저 정보를 가져오기 위해 아래와 같이 kakaoAccountInfo 함수를 호출해줍시다.
getUserInfo 함수의 본문은 아래와 같은데요. /v2/users/me
로 access token을 던지면 유저정보를 받아올 수 있습니다.
이렇게 유저 정보를 가져온 후 보통은 자사 인증 서버에서 자사 서비스에서 사용할 토큰으로 교환하는 과정을 거치게 되는데요. 이 과정을 진행하고 로그인 처리를 마무리 하겠습니다.
간단하게 토큰 교환 과정을 거친 후(라인 1~3), 쿠키로 accessToken, refreshToken을 심어주고 브라우저로 redirect 시켜줍니다(라인 6)
이후 브라우저에서 쿠키를 확인해보면 정상적으로 토큰이 심어졌음을 확인할 수 있습니다.
작성된 모든 코드는 여기서 확인하실 수 있습니다.
소셜 로그인 기능은 각 플랫폼에서 sdk를 제공해주는 경우도 많고 사용할 수 있는 라이브러리의 폭도 넓은 편이라 바닥부터 직접 구현해 볼 일이 많이 없는 것 같습니다.
저도 웹에서 로그인을 구현할 때에는 Next-Auth라는 라이브러리를 주로 사용하곤 하는데요. 최근 로그인 관련 이슈를 해결하다 문득 라이브러리에 너무 의존하다 보니 OAuth에 대해 잘 알고있지 못하다는 생각이 들어 조사하며 포스팅을 작성하게 되었습니다.
이어지는 다음 포스팅에서는 Next-Auth 라이브러리를 뜯어보며 소셜 로그인 기능을 어떻게 추상화해서 제공해주는지 한번 살펴보고자 합니다.
긴 글 읽어주셔서 감사합니다 🙇♂️