node.js) 소셜 로그인 (카카오톡) / 토큰 재발행 , 회원가입 저장

2023. 9. 7. 22:30· Node.js

내가 카카오톡 로그인을 구현하면서 생각한 로직은 이하와 같다.

 

1. 프론트에서 카카오 로그인 API 이용해서 access token 을 발행 받음

-> 이때 email값을 토큰값에 꼭 넣어줘야함 

2. 이메일값을 추출해서 백엔드로 넘겨줌

3. 백엔드는 그 값을 받아서 비밀번호 없이 회원가입을 해줌

4. 로그인시에 기존 로컬 회원가입과 마찬가지로 우리 규칙에 맞는 토큰값을 발행해줌

-> 사용자 입장에서는 토큰이 재발행되는것. 우리의 규칙이란 , 토큰값에 userId값을 넣어주는것

 

카카오톡 API의 설정에 대해 공식문서를 더 깊이 보면 다른 방법이 있을 것 같긴 한데, 제일 간단하고 빠르게 구현하는것을 목적으로 했다. rest api 에서 토큰을 발행받아올 수 있는 방법도 따로 있는데 (백엔드로 받아오는것) , 우리는 이미 로그인 인증 미들웨어와 로컬 회원가입/로그인/로그아웃이 구현되어있고 기존에 개발되어있는것을 최대한 활용하게끔 방향이 잡혀져있어서 , email값만 받아오는것 목적으로 개발했다. 

 

코드를 간단하게 정리해서 보여드림 !

1번과 2번 로직 ! 프론트단이다

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" type="text/css" href="./css/signup.css" />
    <title>SIGNUP html</title>
  </head>
  <body>
    <!-- <a id="kakao" href="/api/login/kakao" class="btn">카카오톡 로그인</a> -->
    <a href="javascript:kakaoLogin();"><img src="https://developers.kakao.com/docs/static/image/ko/m/kakaologin.png" /></a>
    <script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
    <script>
      //0086ddc0be052814652891ce70d445d2
      window.Kakao.init('0086ddc0be052814652891ce70d445d2');
      function kakaoLogin() {
        window.Kakao.Auth.login({
          scope: 'account_email',
          success: function (authObj) {
            window.Kakao.API.request({
              url: '/v2/user/me',
              success: res => {
                const kakao_account = res.kakao_account;
                fetch(`/api/kakao`, {
                  method: 'POST', // 변경이 필요한 경우 PUT 또는 다른 HTTP 메서드 사용
                  headers: {
                    'Content-Type': 'application/json',
                  },
                  body: JSON.stringify({ email: kakao_account.email }),
                });
              },
            });
          },
        });
      }
    </script>
  </body>
</html>

우리는 프론트 개발은 후반에 진행 할 예정이라 테스트 할 수 있는 틀만 만들어놨다.

추후에 스크립트 부분을 js파일로 따로 만들 예정.

카카오 api를 이용해 이메일값만 따로 받아와 백엔드로 넘겨주는 로직이다.

 

-user.controller.js 부분

import { UserService } from '../services';

class UserController {
  _userService = new UserService();
  // 회원가입
  register = async (req, res) => {
    const loginType = true;
    const { email, password, passwordConfirm, type } = req.body;
    try {
      await this._userService.registerUser(email, password, passwordConfirm, type, loginType);
      res.status(201).json({ message: '회원가입에 성공했습니다.' });
    } catch (err) {
      console.error(err);
      res.status(500).json({ message: err.message });
    }
  };

  // 로그인
  login = async (req, res) => {
    const { email, password } = req.body;
    try {
      const { token, userId } = await this._userService.loginUser(email, password);
      console.log(token);
      res.cookie('authorization', `Bearer ${token}`);
      res.status(200).json({ message: '로그인 성공' });
    } catch (err) {
      console.error(err);
      res.status(500).json({ message: err.message });
    }
  };

  //로그아웃
  logoutUser = async (req, res) => {
    try {
      await this._userService.logoutUser(req, res);
      return res.status(200).json({ message: '로그아웃 되었습니다.' });
    } catch (err) {
      console.error(err);
      return res.status(500).json({ message: err.message });
    }
  };

  //회원탈퇴
  deleteUser = async (req, res) => {
    const userId = req.params.userId;
    try {
      const deleteUserData = await this._userService.deleteUser(userId);
      res.status(deleteUserData.status).json({ message: deleteUserData.message });
    } catch (err) {
      console.error(err);
      res.status(500).json({ message: err.message });
    }
  };

  kakao = async (req, res) => {
    const { email } = req.body;
    // const { type } = req.body; //원래 받아줘야하는데 프론트에서 넘겨줄거라 지금은 걍 유저로 하겠음
    const type = 'user';
    const loginType = false;
    const password = null;
    const passwordConfirm = null;
    const { token } = await this._userService.registerUser(email, password, passwordConfirm, type, loginType);

    console.log('성공적인 토큰 >ㅁ<', token);
    res.cookie('authorization', `Bearer ${token}`);
  };
}

export default UserController;

 

개발이 어떤 방식이냐면 , 

컨트롤러쪽에선 소셜로그인과 로컬로그인 , 회원가입이 따로 요청이 들어온다

서비스쪽으로 나갈때 이제 합쳐지게 되는데 !!

 

-user.service.js

import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';

import { UserRepository } from '../repositories';

class UserService {
  _userRepository = new UserRepository();

  //회원가입
  async registerUser(email, password, passwordConfirm, type, loginType) {
    const result = await this._userRepository.findUserByEmail(email);

    //카카오 회원가입이 이미 존재하면 토큰 새로 발급
    if (result && loginType === false) {
      let password = null;
      return await this.loginUser(email, password, loginType);
    }

    //카카오로 로그인이면
    if (!result && loginType === false) {
      return await this._userRepository.registerUser(email, password, type, loginType);
    }
    if (!email) {
      throw new Error('이메일을 입력해주세요');
    }

    if (!password) {
      throw new Error('비밀번호를 입력해주세요');
    }

    if (!passwordConfirm) {
      throw new Error('비밀번호가 일치하지 않습니다');
    }

    if (result) {
      throw new Error('중복된 이메일입니다.');
    }

    if (password !== passwordConfirm) {
      throw new Error('비밀번호가 일치하지 않습니다.');
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = await this._userRepository.registerUser(email, hashedPassword, type, loginType);

    return newUser;
  }

  // 로그인
  async loginUser(email, password, loginType) {
    const user = await this._userRepository.findUserByEmail(email);

    if (!user) {
      throw new Error('회원정보가 일치하지 않습니다.');
    }

    if (loginType === null) {
      const isValidPassword = await bcrypt.compare(password, user.password);

      if (!isValidPassword) {
        throw new Error('회원정보가 일치하지 않습니다.');
      }
    }

    const token = jwt.sign({ userId: user.userId }, process.env.JWT_SECRET, {
      expiresIn: process.env.JWT_EXPIREIN,
    });

    return { token, userId: user.userId }; // token과 userId 값을 같이 반환합니다.
  }

  // 로그아웃
  async logoutUser(req, res) {
    await this._userRepository.logoutUser(req, res);
  }

  // 회원탈퇴
  async deleteUser(userId) {
    const existUserData = await this._userRepository.getUserById(userId);
    if (!existUserData) {
      return {
        status: 404,
        message: '사용자를 찾을 수 없습니다.',
      };
    } else {
      await this._userRepository.deleteUser(userId);
      return {
        status: 200,
        message: '회원탈퇴를 완료하였습니다.',
      };
    }
  }
}
export default UserService;

회원가입을 할때 로컬과 소셜을 따로 받아 loginType을 함께 넘겨준다. 

loginType이 true면 로컬 , loginType이 false면 소셜로 방향을 나눠주고 회원가입을 하면서 데이터를 저장한다.

email값 데이터가 이미 존재하면 로그인 파트로 넘어가게끔 만든다.

로그인으로 넘어오면 토큰 발행이 된다 !

 

를 반증할 레포쪽 !

-user.repository.js

import { User } from '../db/index';

class UserRepository {
  async registerUser(email, password, type, loginType) {
    return User.create({
      email,
      password,
      type,
      loginType,
    });
  }

  async findUserByEmail(email) {
    return await User.findOne({ where: { email } });
  }

  async logoutUser(req, res) {
    res.cookie('authorization', '', { maxAge: 0 });
  }

  async getUserById(userId) {
    return await User.findOne({ where: { userId: userId } });
  }

  async deleteUser(userId) {
    return await User.destroy({ where: { userId: userId } });
  }
}

export default UserRepository;

 

짜잔 !

'Node.js' 카테고리의 다른 글

node.js) 소셜 로그인 (카카오톡) / 토큰 재발행 , 회원가입 저장  (0) 2023.08.24
(TIL)node.js 좀 더 좋은 코드 고민 : 서버 객체화 하기  (0) 2023.07.27
js)import require 에러 모음집 ....  (0) 2023.07.27
node) transaction을 왜 써야할까 ? +에러 해결  (0) 2023.07.21
'Node.js' 카테고리의 다른 글
  • node.js) 소셜 로그인 (카카오톡) / 토큰 재발행 , 회원가입 저장
  • (TIL)node.js 좀 더 좋은 코드 고민 : 서버 객체화 하기
  • js)import require 에러 모음집 ....
  • node) transaction을 왜 써야할까 ? +에러 해결
혬00
혬00
혬00
hyem.js
혬00
전체
오늘
어제
  • 분류 전체보기 (32)
    • 프로젝트 (8)
    • AWS EC2,RDS,S3 (3)
    • Node.js (5)
    • JavaScript (8)
    • Nest.js (2)
    • Docker (1)
    • DB (1)
    • React + Next (ver.14) (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • flask재설치
  • sharp
  • flask
  • venv인터프리터
  • var upload = this.s3.upload(params); ^ TypeError: this.s3.upload is not a function
  • venv재설치
  • Python
  • venv활성화
  • 잘못된 Python 인터프리터를 선택했습니다
  • import "flask" could not be resolved
  • venv 재설치
  • multer-s3-transform
  • venv

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.1
혬00
node.js) 소셜 로그인 (카카오톡) / 토큰 재발행 , 회원가입 저장
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.