프론트에서 카카오 소셜 로그인/회원가입하기 (feat. OAuth 2.0, jwt 토큰)

mogooee·2024년 4월 23일
4

OAuth2.0 방식을 이용해 카카오 소셜 로그인/회원가입을 구현한다.
OAuth는 Open Authorization의 약자로 카카오, 네이버, 구글 등의 사이트의 개인정보로 타사이트를 가입할 수 있는 기능이다. 간편한 회원가입과정으로 유저의 유입을 늘리기 위해 사용한다.

OAuth2.0에 대해서는 이전 포스팅에서 자세하게 다뤘으므로 이번 포스팅에서는 인증과 구현 방식에 대해 자세히 다뤄본다.

프론트엔드 관점에서 작성된 글이며, 서버는 msw로 구축했다.
로그인 인증 방식은 jwt 토큰을 사용하고 access token은 local variable, refresh token을 secure cookie로 저장한다.

  1. 로그인 인증 방식
    • 세션 Id
    • jwt 토큰
  2. 토큰 저장 위치
    • 웹애플리케이션 공격
    • 로컬 스토리지
    • 세션 스토리지
    • 쿠키
    • secure 쿠키
  3. OAuth 로그인/회원가입 flow
  4. 카카오 OAuth 구현하기

로그인 인증 방식

일반적으로 로그인 인증방식에는 token을 사용한다.
인증 방식은 크게 세션 id와 token 방식으로 나뉘어져 있다.
각 방식의 장단점을 알아본다.

세션 id

  • 회원 DB에 유저가 있는지 확인한다.
  • 서버의 유저 정보에 대한 세션을 생성하고 세션 저장소에서 세션 ID를 발급한다.
    • 영화관에서 티켓을 보관용만 찢어 건네주는 것처럼 세션 ID는 사용자에게 전달하고, 메모리에 아이디 사본을 어떤 사용자 것인지 적어서 보관한다.
  • 클라이언트는 세션 ID를 쿠키에 담아 리소스를 요청한다.
  • 서버는 쿠키를 검증하고 세션 저장소에서 유저 정보(세션)을 얻어 응답한다.

장점

  • 세션 ID가 변질되면 해당 세션을 삭제할 수 있다.
  • 길이가 짧은 편이어서, 브라우저 저장에 용이하다.

단점

  • 추가 저장소가 필요하다. (세션 저장소)
  • 로그인을 유지할 경우, 필요한 메모리 공간이 많아지는데 추가 저장소의 메모리 공간은 한정적이므로 서버에 부하가 걸릴 수 있다.

jwt 토큰

구조

  • Header: 토큰 타입, 생성 알고리즘
  • Payload: 토큰에 대한 정보를 key-value 형태로 저장, 디코딩시 누구하 확인할 수 있으므로 민감한 정보가 아닌 단순 식별을 위한 정보를 담아야 함.
  • Signature: secret 키로 암호화한다.

Access Token

  • 회원 DB에 유저가 있는지 확인한다.
  • 서버는 access token을 발급한다.
  • 클라이언트는 access token으로 리소스를 요청한다.
  • 서버는 access token을 검증하여 리소스를 응답한다.

장점

  • 확장성이 뛰어나다.
    • 세션과 달리 토큰은 stateless 하므로 사용자 인증 정보를 서버 측이 아닌 클라이언트 측에 저장한다. 이로써 분산된 시스템을 설계할 수 있으므로 세션 인증 방식의 서버 관리 부담을 완화할 수 있다.
    • 토큰에 선택적인 권한만 부여하여 발급할 수 있다.(카카오 프로필 정보 접근 권한 O, 채팅 권한 X)
  • 추가 저장소가 필요하지 않다.
    • 토큰 생성 & 검증 알고리즘을 통해 토큰을 생성하고 검증한다.

단점

  • jwt 담을 수 있는 정보가 제한적 (payload -> 디코딩하면 볼 수 있음)
  • 유효기간안에 계속 사용 가능하고 중간에 토큰을 삭제할 수 없다.
    • 정보 탈취 가능성이 높다.
    • 강제 로그아웃이 불가능하다.
      => 만료 시간을 짧게 지정해 피해를 줄이자!
세션 방식토큰 방식
장점사용자의 상태를 원하는대로 통제 가능상태를 따로 기억해 둘 필요가 없음
단점메모리에 로그인되어 있는 사용자의 상태를 보관해야 함한 번 로그인한 사용자 상태의 토큰을 삭제할 수 없음

Refresh Token

access token의 정보 탈취 약점을 보완하고자 나왔다.

  • 회원 DB에 유저가 있는지 확인하고, access token과 refresh token을 발급한다.
  • 클라이언트는 token을 안전한 곳에 저장한다.
  • 클라이언트는 access token으로 리소스를 요청한다.
  • 서버는 access token을 검증해서 유효하면 리소스를 응답한다.
  • 만약 access token의 유효기간이 만료되었다면 에러를 응답한다.
  • 클라이언트는 refresh token을 보내 access token 갱신을 요청한다.
  • 서버는 refresh token이 유효하면 새로운 access token을 발급한다.
  • 만약 refresh token의 유효기간이 만료되었다면 로그인을 다시 시도하여야 한다.

장점

  • access token의 유효기간을 짧게 하므로 탈취 위험에 대한 보안이 강호된다.

단점

  • 구현이 복잡해진다.
  • refresh token이 탈취된다면 역시 보안상의 문제가 발생한다.

결론

서버에 별도의 저장 공간이 없어도 jwt 토큰에 있는 정보를 secret key로 복호화하여 사용하기 때문에 보안과 저장 용량 측면에서 큰 이점이 있다.
보안 면에서 access token과 refresh token 두 가지를 발급하여 access token의 유효기간을 짧게 하였다. 상대적으로 유효기간이 길고 access token을 재발급 할 수 있는 refresh token은 안전한 곳에 저장하는 방식을 택했다.

토큰 저장 위치

토큰은 클라이언트 측에서 저장한다.
클라이언트 저장소는 대표적으로 로컬 스토리지, 세션 스토리지, 쿠키가 있다.
이때 보안을 고려하여 저장소를 정해야하는데, 웹앱의 대표적인 취약점은 XSS, CSRF가 있다.
먼저 공격에 대해 알아보고, 그에 따라 저장 위치를 정해보자.

웹애플리케이션 공격

CSRF(Cross-Site Request Forgery - 사이트 사이 위조 요청)

사용자가 자신의 의지와는 무관하게 침입자가 의도한 행위를 서버에 요청하게 만드는 공격
특정 사이트가 사용자를 신뢰하기 때문에 서버에서 발생하는 문제다.
서버로부터 권한을 탈취할 수 있다.

침입자는 하이퍼링크에 자금 전송 요청에 대한 스크립트를 삽입하고 사이트에 로그인할 사람들에게 전송한다.
사용자는 링크를 누르고 의도치 않게 서버로 요청을 보낸다.
서버는 로그인 된 사용자의 요청이기 때문에 정상적으로 인식하고 침입자에게 돈을 전송한다.

  • 방지 대책
    • request Header의 referrer 체크
    • CSRF 토큰 검증
      • 랜덤한 난수를 사용자 세션에 저장하고 모든 요청에 대하여 서버단에서 검증하는 방법
    • CAPTCHA로 사용자가 의도한 요청인지 트리거로 동작하는지 확인
      CAPTCHA

XSS(Cross-Site-Scripting - 사이트 사이 스크립팅)

웹사이트에 의도치 않은 스크립트를 넣어서 실행시키는 것으로 유저의 쿠키 정보를 탈취하거나, 유저 비밀번호를 변경하는 API를 호출할 수 있다.
XSS

쿠키를 탈취하는 스크립트를 사이트에 삽입한다.
사용자가 웹사이트에 접근하면 스크립트가 작동한다.
스크립트를 통해 사용자의 쿠키가 전달된다.

XSS는 크게 세가지 종류다.

  1. Stored XSS
    XSS 공격 스크립트를 웹 사이트 방명록이나 게시판에 삽입하고, 다른 사용자들이 그 글을 확인할 때 스크립코드가 사용자에게 전달되고 쿠키가 침입자에게 전달되는 방식이다.
    스크립트를 웹 서버에 저장하므로 Stored 방식이라 한다.

  2. Reflected XSS
    공격 스크립트가 삽입된 URL을 사용자가 클릭하도록 유도하는 방식이다.
    클릭 요청이 발생하면 바로 스크립트가 반사되어 돌아온다고해서 Reflected 이라 한다.
    보통 피싱 공격에 많이 사용된다.

  3. DOM-based XSS
    사용자 브라우저에서 DOM 환경을 수정하면 공격 스크립트가 실행된다.
    페이지 자체는 변경되지 않지만 DOM에서 발생한 수정으로 인해 페이지에 포함된 클라이언트측 코드가 다르게 실행된다.
    즉 스크립트는 HTML 페이지가 구문 분석이 될 때마다 실행된다.
    다른 XSS 방식과 다르게 서버와 관계가 없다.

  • 방지 대책
    • 쿠키에 중요 정보를 담지 않고 서버에 중요정보를 저장한다.
    • 쿠키에 HTTPOnly 속성을 추가한다.
    • JS로 쿠키에 접근할 수 없다. document.cookie (X)
XSSCSRF
개념사용자가 특정 사이트를 신뢰특정 사이트가 사용자를 신뢰
문제가 발생하는 곳브라우저서버
탈취사용자의 쿠키서버로부터의 권한
방지책1. 쿠키가 아닌 서버에 민감 정보 저장
2. HTTPOnly 쿠키
1. referrer 속성 확인
2. CAPTCHA
3. CSRF Token

웹앱의 대표적인 취약점과 그에 대한 방지책을 알아보았다.
이제 이를 고려하여 어떤 클라이언트 저장소에 토큰을 저장하면 좋을지 알아보자.

로컬스토리지

  • 브라우저가 새로고침되어도 정보가 유지된다.
  • 다른 브라우저에서 열면 정보가 사라진다.
    -> 로컬 스토리지는 JS로 접근이 가능하다.
    따라서 서드파트 자바스크립트 코드에 🔥 XSS 공격 스크립팅이 들어있을 때 로컬스토리지의 토큰이 탈취될 위험성이 있다.

세션 스토리지

  • 현재 탭에서만 정보가 유지된다.
  • 새로고침되어도 유지되지만 다른 탭에서 열면 정보가 사라진다.
    -> 로컬스토리지와 같이 🔥 XSS 공격에 취약하다.

쿠키

  • JS로 접근이 가능하므로 🔥 XSS 공격에 취약하다.
  • 모든 요청에 자동으로 쿠키가 함께 전송되므로 🔥 CSRF 공격 에 취약하다.
  • access token은 인증에 직접적으로 사용되므로 refresh token을 쿠키에 저장한다.
    • refresh token이 탈취되어 새로운 access token을 발급받았어도 access token을 스크립트에 삽입하여 HTTP 요청에 담아 보낼 수 없다면 리소스에 접근할 수 없다. ( ✨ CSRF 방어 )

secure 쿠키

쿠키는 JS로 접근이 가능하므로 보안에 취약하다.
httpOnly, Secure, SameSite 옵션들로 보안을 강화할 수 있다.

  • httpOnly: document.cookie로 접근이 불가능하다. ✨ XSS를 방어 할 수 있다.
  • Secure: HTTPS 요청에만 전송되는 쿠키
  • SameSite: Cross Site로 전송되는 요청의 경우 제한을 둔다. CSRF 공격을 방어할 수 있다.
    • None: 기존 쿠키와 같이 모든 요청에 전송한다. 대신 반드시 secure 속성을 설정하여야 한다.
    • Strict: Cross Site로 쿠키가 전송되지 않는다. 퍼스트 파티 쿠키만 전송된다.
    • Lax: 기본값, 서드파티 쿠키는 전송되지 않지만 예외적인 요청에는 전송할 수 있다.
      • Top Level Navigation(웹 페이지 이동)과, 안전한 HTTP 메서드 요청의 경우 전송된다.
      • Top Level Navigation: <a>, window.location.replace를 통한 이동
      • 안전하지 않은 POST나 DELETE 같은 요청의 경우, Lax 쿠키는 전송되지 않는다.
      • GET처럼 서버의 서버의 상태를 바꾸지 않을 거라고 기대되는 안전한 요청에는 Lax 쿠키가 전송된다.

장점

  • 로컬스토리지만큼 XSS 공격에 취약하지 않다.
    • httpOnly 속성으로 자바스크립트에서 쿠키에 접근을 막을 수 있다.
  • secure 속성으로 HTTPS를 통해서만 전송할 수 있어 보다 보안을 강화할 수 있다.
  • SameSite 속성으로 ✨ CSRF 공격을 방어 할 수 있다.
  • HTTP 요청시 자동으로 보내진다.

단점

  • 🔥 완전히 XSS 공격을 막을 수 있는 것은 아니다.
    • 자바스크립트로 쿠키에 접근할 수 없다하더라도, 자바스크립트를 실행할 수 있다면 HTTP 요청을 보낼 수 있고 쿠키는 자동적으로 요청에 포함된다.
  • 쿠키 최대 사이즈는 4KB 이므로, 큰 JWT 토큰을 사용한다면 쿠키에 저장할 수 없다.
  • API 서버와 쿠키를 공유할 수 없거나, authorization 헤더에 access token을 담아서 요청해야 하는 경우 토큰을 쿠키에 저장할 수 없다.

결론

  • Refresh Token은 쿠키로 발급받는다.

    • httpOnly 속성으로 JS가 읽지 못하도록 한다.(XSS 방어)
    • secure=true HTTPS 통해서 전송
    • SameSite=strict 인증된 웹사이트에서만 토큰 전송하기 (CSRF 방어)
  • Access Token을 HTTP Response로 응답받는다. - 응답받은 Access Token은 local variable에 저장

    • 다른 탭 전환 또는 새로고침시 토큰이 사라지나 Refresh Token이 쿠키에 존재하므로 새로 발급받으면 된다.
  • Refresh Token으로 Access Token 갱신

    • Refresh Token을 이용해 매번 Access Token을 발급받는 것은 보안을 위해 감수해야하는 자원 요청이다.
    • 서버 측에서 캐싱한다면 거의 비용이 들지 않는 네트워크 요청이다.

OAuth 로그인/회원가입 flow

AccessToken & Refresh Token
  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+

client는 우리 서버이고, autorization server와 resource server는 카카오를 의미한다.
프론트와 백엔드를 나누어 어떻게 구현해야 할지 flow를 그려본다.

간편 로그인 버튼을 클릭하면 카카오 로그인창으로 이동하고 인증 후 code를 받는다.

  1. FE: 카카오로 간편 로그인/회원가입 버튼을 클릭하면 카카오 Authorization URL로 리다이렉트한다.
  2. kakao: 유저가 로그인을 통해 인증에 성공하고 정보 제공에 동의하면 카카오 Autorization Server는 설정했던 redirect url의 쿼리로 code를 담아 리다이렉트한다.

인증 code를 우리 서버에 보내면 카카오에 access token을 요청하고 발급받는다.

  1. FE: redirect url에서 렌더링하는 컴포넌트는 쿼리 인자의 code를 파싱하고 로그인 API 호출로 서버(BE)에 code를 전송한다.
  2. BE: FE로부터 받은 code로 카카오의 Autorization Server에 access token을 요청한다.
  3. kakao: code가 유효하면 access token을 응답한다.

access token 토큰으로 카카오에 사용자 정보를 조회하고 회원가입 여부에 따라 로그인 처리한다(우리 서버 전용 토큰 발급).

  1. BE: access token으로 kakao에 필요한 유저 정보를 요청하고 응답받는다. 서비스 회원인지 확인하여 회원이라면 서비스 로그인 처리 후 우리 서버 전용 access token, refresh token을 발행하고 FE에 응답한다.
    • 카카오로 받은 access token은 백엔드가 카카오 Resource server에 정보를 요청하기 위한 것
    • 우리 서버 전용 token은 유저가 우리 서버의 Resource server에 정보를 요청하기 위한 것
  2. FE: 회원가입 여부에 따라 홈(로그인 성공)/회원가입 화면으로 리다이렉트한다.
    • 회원가입한 유저: 로그인 성공 처리 후 홈화면으로 이동
    • 회원가입하지 않은 유저: 회원가입 화면으로 이동

토스에서 소개하는 OAuth 2.0

토스의 개발자 커뮤니티에서 OAuth 2.0 프레임워크를 도식화한 것이 굉장히 깔끔해보여서 이미지를 가져왔다!
위의 내용과 동일하며 과정이 잘 요약되어 있다.

  • 리소스 소유자(Resource Owner): 사용자
  • 클라이언트(Client): 사용자의 정보를 접근하는 제3자의 서비스
  • 인증 서버(Authorization Server): 클라이언트의 접근을 관리하는 서버
  • 리소스 서버(Resource Server): 리소스 소유자의 데이터를 관리하는 서버

카카오 OAuth 구현하기

다음은 카카오 developers의 공식문서의 카카오 로그인 과정이다.
앞선 OAuth flow 와 상당히 유사하다.

그럼 차근차근 OAuth를 구현해보자!

1. 카카오 Authorization URL 만들기 (FE <-> kakao)

우선, FE는 버튼을 클릭하면 카카오 Authorization URL로 리다이렉트 한다.
Authorization URL은 FE, BE 누가 만들던 상관없다.
앱 설정> 팀 관리 > 팀원 초대를 통해 owner 외에 editor를 추가해 함께 관리할 수 있다.

메서드URL
GEThttps://kauth.kakao.com/oauth/authorize

필수 파라미터는 다음과 같다.

이름타입설명필수
client_idString앱 REST API 키
[내 애플리케이션] > [앱 키]에서 확인 가능
O
redirect_uriString인가 코드를 전달받을 서비스 서버의 URI
[내 애플리케이션] > [카카오 로그인] > [Redirect URI]에서 등록
O
response_typeStringcode로 고정O
  • client_id: kakao developers > 내 애플리케이션 > 앱 키의 REST API 값
    • 애플리케이션이 없다면 추가해서 생성한다.
  • redirect_url: 인가 code를 받을 리다이렉트 주소
    • 개발 환경: http://localhost:3000/redirect-auth
    • 배포 환경:
      https://배포주소/redirect-auth
  • response_type: code로 받겠다!

최종적으로 SNS 로그인 버튼을 클릭했을 때 이동할 Authroization URL은 다음과 같다.
다음 URL을 <a> 태그의 href 속성에 넣어 사용자가 버튼을 클릭하면 리다이렉트시킨다.

https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}

주의사항

  1. React를 사용한다면 <Link> 태그는 라우팅을 위한 것으로 외부 주소로 이동이 안된다. 반드시 <a> 태그를 사용하자.
  2. 내 애플리케이션 > 카카오 로그인을 활성화해주어야 code가 정상적으로 발급된다.

2. 토큰 받기 (FE->BE<->kakao)

위 과정을 통해 유저는 Authorization URL에 접근해 카카오 계정에 로그인 후 정보 제공을 동의하고 Redirect URL을 통해 코드를 발급받았다.
FE는 이 코드를 파싱해서 BE에게 로그인 API 요청을 통해 OAuth 로그인해주세요! 라고 요청한다.
그럼 BE는 코드라는 허가증을 가지고 카카오에게 토큰을 발급받는다.

여기서 한번 정리!

  • code: 토큰을 발급받기 위한 허가증, resource owner(유저)에게 발급한다.(유저<->카카오)
  • 토큰: 카카오 정보에 접근하기 위한 권한, code라는 허가증을 가지고 우리 서버가 카카오에게 발급을 요청한다. (서버<->카카오)

🚨 만약 프론트엔드만 구현해도 된다면 토큰 발급 부분은 백엔드가 담당하기 때문에 step4. 로그인/회원가입 처리로 넘어가면 된다.
하지만 JavaScript로 백엔드를 포함해 전체 flow를 구현하고 있다면
1. Authorization Server에게 카카오 토큰을 발급받고
2. Resource Server에게 유저 정보를 얻어오고
3. 서비스 자체 토큰을 발급하는 과정이 필요하다.

토큰을 요청할 URL을 만들어보자.

메서드URL
POSThttps://kauth.kakao.com/oauth/token

필수 파라미터는 다음과 같다.

  • 헤더(header)
이름설명필수
Content-typeContent-type: application/x-www-form-urlencoded;charset=utf-8
요청 데이터 타입
O
  • 본문(body)
이름타입설명필수
grant_typeStringauthorization_conde로 고정O
client_idString앱 REST API 키
[내 애플리케이션] > [앱 키]에서 확인 가능
O
redirect_uriString인가 코드를 전달받을 서비스 서버의 URI
[내 애플리케이션] > [카카오 로그인] > [Redirect URI]에서 등록
O
codeString인가 코드 받기 요청으로 얻은 인가 코드O
client_secretString토큰 발급 시, 보안을 강화하기 위해 추가 확인하는 코드
[내 애플리케이션] > [보안]에서 설정 가능
ON 상태인 경우 필수 설정해야 함
X
  • grant_type: 허가 유형은 code다.
  • client_id: 앱 REST API키
  • redirect_uri: 인가 코드가 리다이렉트된 URI
    • client_id, redirect_uri는 code 요청 파라미터와 같은 값이다.
  • code: 요청으로 얻은 인가코드
    • 유저가 브라우저를 통해 얻은 코드를 FE가 파싱하여 BE에게 API 요청시 body에 담아 보낸 것
      (유저->FE->BE)

application/x-www-form-urlencoded

Header에 컨텐츠 타입을 application/x-www-form-urlencoded 으로 설정했다.
일반적으로 컨텐츠 타입을 application/json 으로 하고 body는 객체를 JSON.stringify(object) 로 직렬화하여 json 형태로 보냈다.
(json 형태로 보내면 KOE010 에러를 만날 수 있다. 나도 만나고 싶지 않았다)
이번에는 HTTP method와, 컨텐츠 타입을 고려해서 body를 작성해야 한다.

MDN에서 HTTP POST 메서드를 살펴보자.

  • application/x-www-form-urlencoded: &으로 분리되고, "=" 기호로 값과 키를 연결하는 key-value tuple로 인코딩되는 값입니다. 영어 알파벳이 아닌 문자들은 percent encoded 으로 인코딩됩니다. 따라서, 이 content type은 바이너리 데이터에 사용하기에는 적절치 않습니다. (바이너리 데이터에는 use multipart/form-data 를 사용해 주세요.)

간단한 예제도 나와있다.

POST / HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom

요약해보면 다음과 같다.

  • json은 { key: value } 형태를 사용하는 것과 달리 urlencoded는 key=value & key=value 형식으로 인코딩한다.
  • body를 URL Encoding 해주어야 한다.
  • 인코딩 과정이 들어가기 때문에 대량의 데이터를 보내는 것에 적합하지 않으므로, 파일이나 이미지 같은 바이너리 데이터가 포함된 데이터를 보낼 때는 multipart/form-data 라는 컨텐츠 타입을 사용한다.

결론적으로 body에 key=value & key=value 형식으로 담고 url 인코딩을 해서 보내주면 된다!
msw에서 fetch한 코드지만 작성법은 비슷하니 참고용으로 올려본다.
client_secret 은 토큰 발급에 보안을 강화하기 위해 추가한 선택 요소다.
사용되는 환경변수들은 env 파일로 옮겨서 관리해야 보안을 강화할 수 있다.

    const authResponse = await ctx.fetch(kakaoOAuth, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8"',
      },
      body: encodeURI(
        Object.entries({
          grant_type: 'authorization_code',
          client_id: 'REST_KEY',
          redirect_uri: 'http://localhost:3000/redirect-auth?provider=kakao',
          code: AUTHORIZE_CODE,
          client_secret: 'secret key',
        })
          .map(([key, value]) => `${key}=${value}`)
          .join('&'),
      ),
    });

드디어 성공적으로 토큰을 발급받았다!

3. 카카오 유저 정보 요청하기 (BE<->kakao)

이제 카카오 Authorization server로부터 access token을 발급받았으므로 이것을 가지고 카카오 Resource server에 유저 정보를 요청할 수 있다.
나는 프로필 이미지, 닉네임, 이메일을 받아와서 서비스 회원가입/로그인에 이용할 것이다.

GET 요청이기 때문에 훨씬 간단한다.
content-type을 urlendcoded로 설정하고 발급받은 access token을 Authorization에 담아 Bearer 인증을 완료한다.
이제 카카오 Resource server가 access token의 진위여부를 판별해서 요청하는 리소스를 응답한다.

    // 발급받은 access token으로 서드 파티 Resource server에 리소스 요청
    const authResponse = await ctx.fetch('https://kapi.kakao.com/v2/user/me', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8"',
        Authorization: `Bearer ${token.access_token}`,
      },
    });

    const authData = await authResponse.json();

4. 로그인/회원가입 처리 (FE, BE)

카카오 유저 정보를 받아왔으므로 우리 서비스에 가입되어있는 유저인지 확인하고 로그인/회원가입 처리를 한다.
회원 DB에 일치하는 이메일이 있으면 서비스에 가입된 유저로 구분했다.
1. 서비스에 가입된 유저: 로그인 처리
2. 서비스에 미가입된 유저: 회원가입 처리

회원가입 처리에 대해서는 추가적인 논의가 필요하다.
앞서 서버에서 받은 유저 정보로 회원가입 절차 없이 자체적으로 서비스에 가입시키고 로그인까지 처리할 수 있다.
또는 서비스 가입을 위한 추가적인 정보를 입력하게 할 수 있다.

다음 사진은 이슈트래커 프로젝트에서 추가로 가입정보를 입력하도록 만든 폼이다.
이메일은 카카오 Resource server에서 받아온 유저 정보를 입력하여 수정할 수 없게 하고, 닉네임은 유저에게 입력받고 validation을 거쳐 가입시킨다.

회원가입 처리

추가적인 정보를 회원에게 받을 경우 FE는 회원가입 폼 컴포넌트를 구현하고 입력받은 정보를 BE에게 보낸다.
또는 BE가 서드파티 Resource server에서 받은 정보로 바로 회원가입 처리를 할 수 있다.

회원가입 처리를 성공적으로 하면 FE는
1. 로그인 페이지로 이동시키거나 (유저가 로그인해야 한다)
2. 로그인 처리하여 홈페이지로 이동시킨다. (서버가 자동으로 로그인시킨다)

BE

  • 회원 DB에 유저 정보를 추가한다. (회원가입 api와 동일한 로직)
  • 서버에서 자동으로 로그인 처리까지 할 경우 토큰을 발급한다. (로그인 처리 부분 참고)

FE

  • 회원가입 폼을 구현해서 BE에 입력받은 정보를 보낸다.
  • 로그인 페이지로 이동시키거나, 홈페이지로 이동시킨다.
  • 홈페이지로 이동시킬 경우 로그인처리 된 것으로 응답받은 유저 정보 및 토큰을 저장한다. (로그인 처리 부분 참고)

로그인 처리

BE

  • 서비스 리소스에 접근 가능한 토큰을 발급한다.

FE

  • access token은 request headers에 저장한다.
  • refresh token은 cookie에 저장한다.
  • 유저 정보를 저장한다. (이후 마이페이지, 프로필 사진 등을 여러 컴포넌트에서 활용하게 된다.)

5. 서비스 자체 토큰 발급하기 (BE->FE)

BE는 로그인에 성공하면 FE에게 서비스 자체 토큰을 발급한다.

왜 서비스 자체 토큰을 발급하는가?

  • 카카오 access token은 카카오 리소스에 접근하기 위한 것이다.
  • 우리 서비스의 리소스에 접근하기 위한 토큰을 발급해야 한다.

프로젝트에서는 access token, refresh token을 발급했다.
FE는 access token을 request headers에 Bearer token 방식으로 전달하고 refresh token은 cookie에 저장한다.

jose 라이브러리를 이용하면 클라이언트에서 JWT 토큰을 발급할 수 있다.

import * as jose from 'jose';

const secret = new TextEncoder().encode('SECRET-ACCESS');
const refresh = new TextEncoder().encode('SECRET-REFRESH);

export const getAccessToken = ({ userId }: { userId: string }) =>
  new jose.SignJWT({ userId }).setProtectedHeader({ alg: 'HS256' }).setIssuedAt().setExpirationTime('2h').sign(secret);

export const getRefreshToken = ({ userId }: { userId: string }) =>
  new jose.SignJWT({ userId }).setProtectedHeader({ alg: 'HS256' }).setIssuedAt().setExpirationTime('2w').sign(secret);

Ref

profile
개발의 숲

0개의 댓글

관련 채용 정보