나의 개발일지(김지헌)
항해 99 78일차 12/05일 본문
오늘은 카카오톡 소셜로그인을 프론트와 맞춰봤다.
기존 코드
import { Router } from 'express';
import passport from 'passport';
import dotenv from 'dotenv'
import callback from '../../utils/kakaojwt'
import passportCogfig from '../../passport/index';
//패스포트 임포트 해줘야 가능하다.
passportCogfig();
dotenv.config()
const router = Router();
// kakao로 요청오면, 카카오 로그인 페이지로 가게 되고, 카카오 서버를 통해 카카오 로그인을 하게 되면, 다음 라우터로 요청한다.
router.get('/',passport.authenticate('kakao', {scope: ['profile_nickname', 'profile_image'],})); //scope 속성
router.get('/callback'callback)
//index.ts
import kakao from './kakaoSrategy';
export default () => {
kakao(); //카카오 실행
};
//kakaoSrategy.ts
import passport from 'passport';
import dotenv from 'dotenv';
import { User } from '../database/models/user';
import { profile } from '../interface/user';
dotenv.config();
const KakaoPassport = require('passport-kakao').Strategy;
//Strategy => 카카오톡 스토리지 안에있는 클래스 적용
//KakaoPassport => 인터페이스 및 클래스 사용
export default () => {
//passport 카카오가 정한 인터페이스
passport.use(
new KakaoPassport(
{
clientID: process.env.KAKAO_ID!,
callbackURL: process.env.KAKAO_URL!,
},
async (accessToken: string, refreshToken: string, profile:profile, done: any) => {
try {
const profileImg = profile._json.properties.profile_image;
const kakaoId = profile._json.id;
const nickname = profile._json.properties.nickname;
const exUser = await User.findOne({
// 카카오 플랫폼에서 로그인 했고 & snsId필드에 카카오 아이디가 일치할경우
where: { kakaoId, provider: 'kakao' },
});
// 이미 가입된 카카오 프로필이면 성공
if (exUser) {
done(null, exUser); // 로그인 인증 완료
} else {
// 가입되지 않는 유저면 회원가입 시키고 로그인을 시킨다
const newUser = await User.create({
kakaoId: kakaoId,
nickname: nickname,
provider: 'kakao',
profileImg: profileImg,
});
done(null, newUser); // 회원가입하고 로그인 인증 완료
}
} catch (err) {
console.error(err);
done(err);
}
}
)
);
};
//kakaojwt.ts
import { NextFunction, Request, Response } from "express";
import passport from "passport";
import jwt from "jsonwebtoken";
import { Users } from "../interface/user";
import bcrypt from 'bcrypt';
import User from "../database/models/user";
export default(req:Request, res:Response, next:NextFunction) => {
try {
console.log("test");
passport.authenticate(
"kakao",
{ failureRedirect: "/login" }, // 실패하면 '/user/login''로 돌아감.
async (err:Error, user:Users) => {
console.log(user)
if (err) return next(err);
const { userId } = user;
const accessToken = jwt.sign(
{ userId: userId },
process.env.JWT_KEY!,
{ expiresIn: "3h" }
);
const refreshToken = jwt.sign(
{ userId: userId },
process.env.JWT_KEY!,
{ expiresIn: "7d" }
);
const salt = bcrypt.genSaltSync(Number(process.env.SALT_ROUND!));
const RefreshToken = bcrypt.hashSync(refreshToken, salt);
await User.update({RefreshToken}, {where : {userId}})
res.cookie("refreshToken", refreshToken);
res.cookie("accessToken", accessToken);
// result = { userId, accessToken, refreshToken, nickname };
res.status(200).json({
message: "로그인 성공",
accesstoken: accessToken,
refreshtoken: refreshToken,
});
// res.redirect(
// );
}
)(req, res, next);
} catch (error) {
next(error);
}
};
프론트와 맞춰보기 전에 예상을 했을 때 짯던 코드는
프론트에서 우리 /kakao로 요청을 보내면 /kakao get 라우터에 있는 passport.authenticate가 실행 되며 index.ts로 이동해서 kakao()함수를 실행한다.
그러면 kakaoSrategy.ts로 이동을 해서 로직을 수행하고 kakao/callback을 호출하면서 kakaojwt.ts에있는 토큰 발급 로직과 쿠키에 토큰을 전송 해주는 방식으로 회원가입과 로그인을 백앤드에서 동시에 처리하는 로직으로 구현 하였고,
프론트는 인가코드를 받아서 백앤드로 보내주는 로직으로 구현이 되어있기도 하고 백앤드에서 짠 로직으로 맞춰보았지만 페이지 리다이렉트 되지 않고 오류를 뱉어내며 여러가지 문제가 많았다.
그래서 프론트에서 구현 했는 프론트에서 직접 카카로 api로 호출해서 로그인을 하면 인가 코드를 백엔드로 보내주는 방식으로 구현을 했다.
//kakaojwt.ts
import jwt from "jsonwebtoken";
import bcrypt from 'bcrypt';
import axios from "axios";
import { NextFunction, Request, Response } from "express";
import User from "../database/models/user";
import { Unauthorized } from "./exceptions";
export default async (req:Request, res:Response, next:NextFunction) => {
try{
//토큰 생성 및 토큰 전달 함수
const kakaojwt = async (userId:number)=>{
const AccessToken = jwt.sign(
{ userId: userId },
process.env.JWT_KEY!,
{ expiresIn: "3h" }
);
const RefreshToken = jwt.sign(
{ userId: userId },
process.env.JWT_KEY!,
{ expiresIn: "7d" }
);
const salt = bcrypt.genSaltSync(Number(process.env.SALT_ROUND!));
const refreshToken = bcrypt.hashSync(RefreshToken, salt);
await User.update({refreshToken}, {where : {userId}})
res.cookie("refreshToken", RefreshToken);
res.cookie("accessToken", AccessToken);
res.status(200).json({
message: "로그인 성공",
accesstoken: AccessToken,
refreshtoken: RefreshToken,
})
}
const {authorization}=req.headers;
if(!authorization) throw new Unauthorized("토큰 정보가 없습니다.")
const { data: kakaoUser } = await axios('https://kapi.kakao.com/v2/user/me', {
headers: {
Authorization: `Bearer ${authorization}`,
},
}); //유저 정보를 받아온다
console.log(kakaoUser,"<=카카오 에서 받아옴")
const profileImg:string = kakaoUser.properties.profile_image
const kakaoId:number = kakaoUser.id
const nickname:string = kakaoUser.properties.nickname
const provider:string = 'kakao'
const email:string = kakaoUser.kakao_account.email
const exUser = await User.findOne({
where : {kakaoId, provider}
})
if(!exUser){
const newUser = await User.create({kakaoId,nickname,provider,profileImg,email});
console.log(newUser,"<=저장한 값")
const { userId } = newUser;
console.log("회원가입 했어여~")
kakaojwt(userId)
}else{
const { userId } = exUser
console.log("회원가입 되어있어여~")
kakaojwt(userId)
}
}catch(err){
console.log(err)
next(err)
}
}
흐름을 보면
1.프론트에서 인가 코드를 받아서 헤더에 authorization키값에 인가 코드를 담아서 백앤드로 넘겨준다.
2.req.headers로 받아온 토큰을 구조분해 할당으로 authorization 뽑아내서 https://kapi.kakao.com/v2/user/me로 인가 코드를 담아서 axios콜을 보내고 토큰에 해당하는 유저정보를 받아온다.
3.받아온 정보를 가지고 유저가 있는지 확인 한다. 유저가 없다면 회원가입을 시키고 kakaojwt함수에 userId를 담아서 실행 시킨다.
3-1. 등록된 회원이라면 바로 kakaojwt 함수에 userId를 담아서 실행 시킨다.
4. 프론트는 응답값에 있는 status로 구분해서 200이면 메인페이지 , 그 외 다른 코드면 로그인 페이지로 리다이렉트 시킨다.
프론트와 맞춰가면서 코드를 짜면 좋았을꺼 같지만 서로 처음 구현해보는 api이고 다양한 시도를 하다가 구현한 방식이 달라서 나는 문제였고 다시 보면 그렇게 어려웠던 문제는 아닌거 같다.
'항해 99' 카테고리의 다른 글
항해 99 81일차 12/08일 (0) | 2022.12.09 |
---|---|
항해 99 79일차 12/06일 (0) | 2022.12.07 |
항해 99 77일차 12/04일 (0) | 2022.12.05 |
항해 99 76일차 12/03일 (0) | 2022.12.04 |
항해 99 75일차 12/02일 (0) | 2022.12.03 |