Next.js - 카카오 로그인 구현

tunggary·2022년 3월 26일
3

Next.js

목록 보기
7/7
post-thumbnail

이번 프로젝트에서 로그인 부분을 담당하게 되었다.
카카오 로그인과 이메일 로그인을 구현해야 하는데, 이미 백엔드 개발자분께서 만들어 놓으신 api가 있어서 쉽게 할 수 있을 것 같았지만...
중간에 내가 알고있는 방식대로 되지 않아 새로운 방식을 찾아서 하느라 고생을 좀 했다 😂
그 과정에서 배운 내용들을 정리해 보고자 한다.

카카오 로그인

먼저 카카오 로그인을 구현한 내용을 보기 앞서 카카오 로그인의 과정을 살펴보면
이 그림(출저: https://data-jj.tistory.com/53)과 일치한다.
내가 프론트에서 다룬 부분은 1번, 2번, 3번, 8번 인데, 이전에 React를 사용한 프로젝트에서 했던 방식으로 하려했고, 그 계획은 이랬다.

첫째로 kakao의 api를 통해 인가코드를 받아온다.

둘째로 받아온 인가코드를 api를 통해 서버에게 넘긴다.

셋째로 api는 리턴값으로 JWT를 전달해 주는데 이를 decode하여 user 정보로 활용하고 localStorage에 저장하여 로그인 정보를 유지하려고 했다.

이때 세번째 과정에서 문제가 발생했다 👿

👊 문제 원인

nextjs를 사용하면서 pre-rendering을 하기위해 Server-side에서 데이터를 fetching 하는데, 이때 user의 데이터가 필요하여 JWT를 decode해서 가져와야 한다. 하지만 Server-side에서 localStorage를 접근하지 못해 에러가 발생한 것이다.

🤔 해결 과정

문제를 해결하기 위해 구글링을 하던중, 보통 JWTcookie에 저장하여 관리하는 것을 알 수 있었다. 그 과정에서 localStorage에 저장했을때 발생하는 보안적인 이슈도 알 수 있었다. 물론 지금 프로젝트에서는 Access TokenRefresh Token을 카카오에서 알아서 관리하고 갱신시켜 주기 때문에 상관 없지만, 나중에 직접 Access TokenRefresh Token을 관리하게 될 수 있으므로 배워두면 좋을 것 같다

localStorage 저장 방식
localStorage에 저장하게 되면은 XSS공격에 의해 JWT를 탈취당할 수 있다!
https://academind.com/tutorials/localstorage-vs-cookies-xss

cookie 저장 방식
cookie에 저장하는 방식 또한 XSS, CSRF 공격에서 자유로울 수 없지만, cookie의 SameSite, httpOnly, Secure 등의 속성으로 보안 이슈를 어느정도 해결할 수 있다. 그리고 JWT를 다른 도메인에서 사용할 일이 없고, api 서버도 같은 도메인을 사용하면 Access TokenRefresh Token모두 cookie에 담아 관리할 수 있다. 이에 관련된 더 자세한 내용은 이 게시글, 게시글들을 확인해보면 된다!

cookie를 설정하는 과정을 클라이언트 코드에서 숨기고 pre-rendered 페이지에 로그인 정보를 적용하기 위해 nextjs의 api-route 기능을 사용하였다.

👍 문제 해결

첫째로 kakao의 api를 통해 인가코드를 받아온다.

둘째로 받아온 인가코드를 /api/loginKakao 경로의 api를 호출하여 next 서버로 넘긴다.

셋째로 next 서버에서 우리 api를 호출하여 인가코드를 우리 서버로 전달한다.

넷째로 리턴 받은 JWTset-header를 통해 cookie에 저장한다. error를 리턴 받은 경우 error 메세지를 클라이언트에 전달한다.

클라이언트 코드


  // 1.kakao에서 인증코드 받아오기
  const login = () => {
    window.Kakao.Auth.login({
      throughTalk: false,
      success: function (authObj) {
        onSuccess(authObj);
      },
      fail: function (err) {
        console.log('카카오에서 인증코드 받아오는 과정에서 오류발생', err);
        alert('카카오에서 인증코드 받아오는 과정에서 오류발생');
      },
    });
  };

  
  const onSuccess = async (res) => {
    // 2.인증코드를 받아오는데 성공하면 next서버로 인증코드를 보냄
    await axios
      .post('/api/loginKakao', {
        token_type: res.token_type,
        access_token: res.access_token,
        expires_in: res.expires_in,
        refresh_token: res.refresh_token,
        refresh_token_expires_in: res.refresh_token_expires_in,
      })
      .catch((err) => {
        console.log(err);
        alert('서버에서 오류발생');
      });
    
	//로그인 성공 후 이전 홈으로 이동
    router.push('/');
  };

api server 코드

import axios from 'axios';
import { LOGIN_WITH_KAKAO } from '/gql/_mutation';

export default async function loginKakao(req, res) {
  const {
    body: {
      token_type,
      access_token,
      expires_in,
      refresh_token,
      refresh_token_expires_in,
    },
  } = req;
	
  //우리 서버로 토큰을 넘기는 과정
  const { data } = await axios
    .post(process.env.NEXT_PUBLIC_API_SERVER, {
      query: LOGIN_WITH_KAKAO,
      variables: {
        access_token: access_token,
        expires_in: expires_in,
        refresh_token: refresh_token,
        refresh_token_expires_in: refresh_token_expires_in,
        token_type: token_type,
      },
    })
    .catch((err) => {
      res.status(500);
    });
  
  //쿠키에 저장
  res.setHeader(
    'Set-Cookie',
    `celebstock=${data.data.loginKakao.jwt}; path=/; domain=.celebstock.kr; Max-Age=${refresh_token_expires_in}`,
  );
  res.status(200).send();
}

보완해야할 점

cookie에 저장만 했을 뿐 cookie에 속성을 제대로 지정해 주지 않았다. httpOnly, samesite와 같은 속성은 이후에 좀 더 확인해보고 수정을 해야할 것 같다.

References

  1. yaytomato님의 velog
  2. arais님의 velog

0개의 댓글