passport 로컬, 카카오 적용(2)

복준우·2023년 7월 12일
0

passport

목록 보기
2/2

이번 포스트는 passport-kakao를 사용하지 않고 passport-local를 이용해 카카오 로그인을 처리한 방법에 대한 포스트입니다.

기존(1)에서 passport-kakao를 이용하지 않은 이유에 대한 짧은 설명이 있었다. 다시 간단히 말해 카카오로 로그인을 하게되면 내가 필요한 정보를 DB에 저장을 해야하기 때문이였다.

카카오로그인을 구현하는 방법은 kakao-developers에서 제공하는 REST APIJavascript SDK 에 대한 구현방법이 안내되어있다. 그중 나는 JavaScript를 이용한 kakao.Auth.login로 구현을 하였다.
kakao.Auth.login은 팝업형식으로 클라이언트에서 모든 인증처리가 가능하다.

passport-kakao를 사용하지 않은 이유

kakaoStrategy를 사용할 경우, 성공적인 인증에 대한 콜백으로 페이지를 리디렉션한다. 그러나 나의 목적은 프로젝트에서 로그인 이력이 없는 경우 클라이언트로부터 닉네임을 모달 창을 통해 요청받는 것이다. 이 방법이 실패하여, passport-kakao를 사용하지 않고 카카오 JavaScript SDK를 사용하여 로그인 요청을 보내고, 해당 정보를 백엔드에서 passport의 login 함수를 사용하여 passport 전략을 직접 처리하여 프로젝트의 로그인 처리를 진행했다.

router.get('/kakao', passport.authenticate('kakao'));

// 카카오 로그인이 되면, 카카오 redirect url 설정에 따라 이동
router.get(
   '/kakao/callback',
   // passport 로그인 전략에 의해 kakaoStrategy로 가서 
	 // 카카오계정 정보와 DB를 비교해서 회원가입시키거나 로그인 처리
   passport.authenticate('kakao', {
			// kakaoStrategy에서 실패한다면 '/login' 로 Redirect
      failureRedirect: '/login',
   }),
   // kakaoStrategy에서 성공한다면 콜백 실행
   (req, res) => {
      res.redirect('/');
   },
);

kakao-developers에서 웹 플랫폼 등록

  1. 로그인을 하고 애플리케이션 추가

  2. Web 플랫폼 등록

    • Web 애플리케이션으로 카카오 로그인을 구현할 계획(개발 환경에 맞는 호스트 등록)
  3. 카카오 로그인 활성화

  4. Redirect URI 등록

    • 개발모드로 진행하기 때문에 localhost:3000 등록(개발 환경에 맞는 호스트 등록)
  5. 카카오로 부터 받을 정보 설정

  6. JavaScript SDK 사용을 위한 JavaScript키 확인

    • 앱 키는 중요한 정보이기때문에, 노출되어서는 안된다.

Kakao.Auth.login 기본구현 방법

  1. 카카오 Javascript SDK 등록

    • 카카오에서 제공하는 Javascript SDK를 내가 만든 프로젝트에 추가해야 한다.
      나의 경우는 next.js를 사용하고 있어 /pages/_app.tsx 경로에 해당 코드를 입력해야 한다.
    import Head from 'next/head';
    
    <Head>
    	<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
    </Head>
  2. 발급된 JavaScript 키를 이용하여 kakao 기능을 수행할 수 있도록 초기화 작업

    export const kakaoInit = () => {
        const kakao = (window as any).Kakao;
        if(!kakao.isInitialized()) {
            kakao.init('발급된 JavaScript Key를 입력하세요');
        }
    
        return kakao;
    }
  3. 카카오 로그인을 하기위한 버튼 구현

    <button onClick={KakaoLoginButton} type="button">카카오 로그인</button>
  4. 로그인 코드

    • 사용자 정보를 가져온 뒤, 클라이언트에서 모든 인증처리를 진행한다.
    const kakaoLogin = async () => {
      // 카카오 초기화
      const kakao = kakaoInit();
    
      // 카카오 로그인 구현
      kakao.Auth.login({
        success: () => {
          kakao.API.request({
            url: '/v2/user/me', // 사용자 정보 가져오기
            success: (res: any) => {
              // 로그인 성공할 경우 정보 확인 후 /kakao 페이지로 push
              console.log(res);
              Router.push('/kakao');
            },
            fail: (error: any) => {
              console.log(error);
            },
          });
        },
        fail: (error: any) => {
          console.log(error);
        },
      });
    };
    • 로그인 요청 결과, 사용자 토큰이 발급되며, 이 토큰은 SDK 내부적으로 사용되므로 별도의 처리가 필요하지 않다. 그러나 로그인에 성공한 경우, 프로젝트에서 로그인 및 회원 가입 처리를 해야 한다. 인증에 성공하면 서비스의 로그인 처리를 위해 success 콜백 함수를 사용한다.

위와 같이 kakao.Auth.login을 이용한 방법에 대해 알아보았다.
이제 내가 프로젝트에 적용한 방법은 아래와 같다.

프로젝트에 코드에 적용한 방법

kakao.Auth.login은 카카오에서 요청 결과에 따라 사용자의 정보를 토큰으로 발급해주는 용도이기 때문에.
프로젝트에서는 별도의 회원가입과 로그인 처리를 해주어야 한다.

  1. success 콜백을 통해 카카오에서 받은 사용자 토큰을 백엔드와 통신하여 정보 전달

    • success 콜백에서 받아오는 정보 확인
    success: (res: any) => {
    	// 로그인 성공할 경우 정보 확인
    	console.log(res);
    },

    success 콜백에서는 로그인이 성공했을 때 받아온 정보를 확인한다.
    나의 경우 카카오 개발자 콘솔에서 동의 항목으로 체크한 항목에 대한 정보를 포함하는데 동의 항목에 대한
    요청이 없거나 체크한 항목이 없다면, 기본적인 사용자 정보를 카카오에서 제공한다.

  2. 백엔드에서 id의 값을 통해 클라이언트 요청

    • 백엔드는 카카오에서 제공받은 id 값을 사용하여 회원가입 및 로그인 처리를 진행
    1. success 콜백에서 받은 정보(id)를 백엔드로 전달

      success: async (res: IKakaoLoginSuccess) => {
        const response: ISignupResult | ILoginResult = await kakaoLogin({
          id: res.id,
        });
      },
      
      // kakaoLogin API
      export const kakaoLogin = async (data: { id: number }) => {
        const response = await apiClient({
          method: 'post',
          url: '/user/kakao',
          data,
        });
        return response.data;
      };
    2. 백엔드는 받은 id 값을 사용하여 유저 정보를 찾고, 요청에 대한 응답을 제공

      • 해당 id로 검색한 정보가 없으면, 이는 프로젝트에 처음으로 로그인을 요청한 경우
      router.post('/kakao', async (req, res, next) => {
        const kakaoLogin = req.body;
      
        const client = await _client;
        const userdb = client.db('TripLogV2').collection('user');
        const user = await userdb.findOne({ id: kakaoLogin.id });
      
        if (!user) {
          return res.send({
            type: 'signup',
            success: true,
            message: '닉네임 정보가 필요합니다.',
          });
        }
      });
      • 해당 id로 검색한 정보가 있으면, 이는 프로젝트에 이전에 카카오 로그인을 한 적이 있는 경우
      router.post('/kakao', async (req, res, next) => {
        const kakaoLogin = req.body;
      
        const client = await _client;
        const userdb = client.db('TripLogV2').collection('user');
        const user = await userdb.findOne({ id: kakaoLogin.id });
      
        return req.login(user, async (error) => {
          if (error) {
            console.error(error);
            return next(error);
          }
      
          return res.send({
            type: 'login',
            success: true,
            message: '카카오 로그인이 성공했습니다.',
            nickname: user.nickname,
          });
        });
      });
  3. 클라이언트에서는 회원가입 및 로그인 요청에 따라 처리를 수행

    • kakao.Auth.login 코드
    // 요청 정보를 저장하는 useState
    const [result, setResult] = useState<ISignupResult | ILoginResult>({
    	type: 'signup',
      success: false,
      message: '',
    });
    
    // 모달창의 상태를 저장하는 useState
    const [show, setShow] = useState(false);
    
    // 카카오 토큰의 개인 id의 상태를 저장하는 useState
    const [kakaoId, setKakaoId] = useState(0);
    
    kakao.Auth.login({
      success: () => {
        kakao.API.request({
          url: '/v2/user/me',
          success: async (res: IKakaoLoginSuccess) => {
            const response: ISignupResult | ILoginResult = await kakaoLogin({
              id: res.id,
            });
    				// signup의 경우
            if (response.type === 'signup') {
              setResult(response);
              setShow(response.success);
              setKakaoId(res.id);
            }
    				// login의 경우
    				if (response.type === 'login') {
    					setResult(response);
    	        setShow(response.success);
            }
          },
          fail: () => {
            const result: ISignupResult = {
              type: 'signup',
              success: false,
              message: '카카오 회원가입 도중에 문제가 발생했습니다.',
            };
            setResult(result);
            setShow(true);
          },
        });
      },
      fail: () => {
        const result: ILoginResult = {
          type: 'login',
          success: false,
          message: '카카오 로그인 도중에 문제가 발생했습니다.',
          nickname: '',
        };
        setResult(result);
        setShow(true);
      },
    });
    
    // 컴포넌트
    return (
    	<>
    	  <button onClick={KakaoLoginButton} type="button" >카카오 로그인</button>
    
        {result.type === 'signup' && (
    	    <KakaoFormModal show={show} setShow={setShow} id={kakaoId} />
        )}
        {result.type === 'login' && (
    	    <SignSuccess show={show} setShow={setShow} result={result} />
         )}
      </>
    );

    백엔드에서 받은 result.type 정보를 기반으로 모달창을 표시하여 사용자에게 어떤 응답이 발생했는지 알려준다.
    <KakaoFormModal />은 회원가입이 필요한 경우에 사용되며, 사용자로부터 닉네임을 입력받는 모달창을 표시한다.
    <SignSuccess />는 로그인이 성공한 경우에 사용되며, 로그인 성공 메시지를 보여주어 사용자에게 알린다.

참고자료

https://jjnooys.medium.com/nodejs-passport-kakao-회원가입-후-자동-로그인-구현하기-7d1be78e29ea
https://developers.kakao.com/
https://www.passportjs.org/packages/passport-kakao/
https://jforj.tistory.com/237

profile
사람들에게 하나의 문화를 선물해줄 수 있는 프로그램을 개발하고 싶습니다.

0개의 댓글