Project Shall-We-Health #3-3 네이버 OAuth 로그인 구현

안광의·2021년 12월 11일
0
post-thumbnail

시작하며

Bare Minimum 단계에서 맡았던 기능 구현을 마치고 Advanced와 Nightmare 단계의 기능들의 구현을 시작했는데 그 중 네이버 소셜로그인 기능을 맡게 되었다. OAuth 로그인 관련 부분은 그동안 프로젝트에서 구현된 코드를 보고 이해하고는 있었지만 항상 다른 팀원분이 담당했었고 카카오 로그인만 구현해왔기 때문에 좋은 공부가 되었다.

네이버 로그인 구현

네이버 API 관련 설정

네이버 소셜 로그인을 구현하기 위해서는 네이버 Developers 사이트에서 내 애플리케이션을 등록하고 API 설정을 해주어야한다. 카카오 API와는 다르게 애플리케이션 검수를 받기 전에는 관리자와 추가로 등록 가능한 5개의 테스트 계정만 로그인이 가능하기 때문에 추후 배포를 마친 후 검수를 받을 예정이다.

로그인에 필요한 필수정보(이메일, 닉네임)를 선택하고 서비스URL과 Callback URL을 설정해주었다. 현재 개발중인 상태이기 때문에 리액트에서 실행시 사용하는 http://localhost:3000로 설정해 주었고 토큰을 받아 서버에 요청을 보낼 수 있도록 /naver로 Callback URL을 설정하였다. (해당 컴포넌트에서 처리할 수 있도록 Route 설정을 해줄예정)

클라이언트

//index.html
...
<head>
<script
      type="text/javascript"
      src="https://static.nid.naver.com/js/naveridlogin_js_sdk_2.0.0.js"
      charset="utf-8"
    ></script>
</head>
...
//App.js
function App() {
  ...

  return(
    	...
	<Route path='/naver' component={NaverLogin} />
	'''
)

네이버 로그인을 사용할 수 있도록 index.html의 head에 관련 script를 추가해주었고 Callback URL에 해당하는 path를 설정해주고 해당 컴포넌트를 생성하였다.

//Login.js
export default function Login() {
  '''
    const initializeNaverLogin = () => {
    const naverLogin = new window.naver.LoginWithNaverId({
      clientId: {/*Client ID*/},
      callbackUrl: 'http://localhost:3000/naver', 
      isPopup: false, // popup 형식으로 띄울것인지 설정
      loginButton: { color: 'green', type: 1, height: '47' },
    });
    naverLogin.init();
  };
  
    useEffect(() => {
    initializeNaverLogin();
  }, []);
  '''
  return(
    ...
    <div className="grid-naver" id='naverIdLogin'></div>
    ...
  )
}

로그인 컴포넌트에 네이버에서 제공하는 로그인 버튼을 생성되고 버튼 클릭시 네이버 로그인이 실행될수 있도록 naverLogin 함수를 생성하고 useEffect로 페이지 렌더링시 실행되도록 하였다.

//NaverLogin.js
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';
import Loading from '../components/etc/Loading';
import axios from "axios"


export default function NaverLogin() {

    const location = useLocation();  

  const getNaverToken = () => {
    if (!location.hash) return;
    const token = location.hash.split('=')[1].split('&')[0];
    axios.post(`${process.env.REACT_APP_SERVER_API}/user/naver-login`, {
        token
    }, {
        withCredentials: true
    })
    .then((res)=> {
        window.location.replace('/')
    })
    .catch(()=>{
      /*에러 핸들링*/
    })
  };


  useEffect(() => {
    getNaverToken();
  }, []);

  return (
    <div className='naverlogin-container'>
    <Loading/>
    </div>
  );

}

네이버 로그인 시 /naver#accessToken={해당유저의 액세스 토큰} 형태의 callback url로 접속이되기 때문에 해당 토큰을 서버에 전달할 수 있도록 설정하였는데, 토큰은 useLocation로 받아오고 axios 요청으로 서버에 전달하였다.

처음에는 클라이언트에서 네이버에 유저정보 요청을 보내려고 시도하였으나 CORS 에러가 발생하였다. 보안상의 이유로 서버 환경에서만 유저정보를 요청할 수 있기 때문에 토큰을 서버에 전달하고 서버에서 관련 요청을 보내야 한다.

서버

네이버 로그인과 관련된 대부분의 포스팅이 클라이언트에서 token을 요청하는 과정만 있고 서버에서 유저 정보를 받아오는 내용이 없어서 네이버측의 가이드를 참고해야 했는데 API의 사용법이 달라진 부분이 반영이 되어 있지 않아서 시간이 더 걸렸다.

네이버 측의 가이드 상에는 네이버 로그인 연동 요청을 보낸 후 code와 state를 받아서 해당 데이터로 다시한번 요청을 보내서 유저정보를 받을 수 있는 접근 토큰을 받는 단계로 안내가 되어있었는데, 실제로 로그인 연동 요청을 보냈더니 중간 과정없이 accessToken을 받을 수 있었다. 가이드에 반영이 되어있지 않거나 혹은 검수되기 전 단계의 애플리케이션만 해당되는 부분일 수도 있어서 추후에 확인이 필요할 것 같다.

//naver-login 응답
const jwt = require("jsonwebtoken");
const { User } = require("../../models");
const axios = require("axios");

module.exports = async (req, res) => {
    const { token } = req.body
    const userData = await axios.get('https://openapi.naver.com/v1/nid/me', {
        headers : {
            'Authorization' : `Bearer ${token}`,
        }
    })
    const { email, nickname } = userData.data.response;
    const isOauth = 1

  try {
    const loginData = await User.findOne({
      where: { email },
      attributes: ["email", "createdAt"],
    });


    if (loginData) {
      /* 회원가입이 되어있을 때 */

      const accessToken = jwt.sign(loginData.dataValues, process.env.ACCESS_SECRET);

      res.cookie("accessToken", accessToken)
        .status(200)
        .end();
    } else {
      /* 회원가입 안되어있을 때 */
      const newLoginData = await User.create({ email, isOauth, nickname, });
      const accessToken = jwt.sign(newLoginData.dataValues, process.env.ACCESS_SECRET);
      res.cookie("accessToken", accessToken)
        .status(201)
        .end();
    }
  } catch (err) {
    console.log(err);
    throw err;
  }
};

클라이언트에서 서버에 accessToken을 전달해주면 서버에서 네이버측에 해당 유저정보를 받아 로그인 과정을 거쳐 Shall-We-Health에서 사용하는 accessToken을 쿠키에 담아 전송하는 형태로 코드를 작성하였고 회원이 아닌 경우 유저정보를 데이터베이스에 생성하고 토큰을 전달해주는 형태로 코드를 작성하였다.

마치며

예정된 계획보다 빠르게 프로젝트가 진행되어서 추가적인 기능도 구현할 수 있을 것 같다. 채팅기능 구현을 위한 socket.io 파트는 다른 팀원 분이 담당하셔서 그동안 Nightmare 단계의 다크모드 구현을 맡을 예정이다. 관련된 포스팅을 찾아보니 여러 방식 중에 localStorage에 다크모드 여부를 저장하고 CSS 변수를 사용해 color를 제어하는 형태가 프로젝트와 잘 맞을 것 같아서 해당 방식으로 구현할 예정이다.

profile
개발자로 성장하기

0개의 댓글