항해 99

항해 99 78일차 12/05일

코딩이좋아요 2022. 12. 6. 03:08

오늘은 카카오톡 소셜로그인을 프론트와 맞춰봤다.

기존 코드

더보기
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이고 다양한 시도를 하다가 구현한 방식이 달라서 나는 문제였고 다시 보면 그렇게 어려웠던 문제는 아닌거 같다.