내가 카카오톡 로그인을 구현하면서 생각한 로직은 이하와 같다.
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 |