오늘 뭐했냐/함께했던 작업들

23.08.08 passport-kakao

스스로에게 2023. 8. 13. 20:21

 한 번 로컬로 만들었으니 카카오는 큰 문제없을 줄 알았으나 전혀 아니었다. 

 

  1. Error: Login sessions require session support
    패스포트가 세션을 기본적으로 사용하는 것을 기본값으로 해서 세션을 설치했다.
  2. TokenError: authorization code not found
    카카오 로그인 요청 시 인가 코드를 받는데 이게 틀리면 에러가 생긴다.
  3. Error: secret option required for sessions
    첫 번째 에러와 마찬가지로 세션에 대한 보안 설정이 필요했다.
  4. KOE006
    Redirect URI이 등록되지 않은 경우 카카오 개발자 사이트에서 Redirect URI을 잘못 등록해서 이런 문제가 생겼다.
  5. KOE320
    동일한 인가 코드 다시 사용됨 →
    포스트맨으로 테스트를 해보던 중 이번엔 사용했던 인가 코드를 재사용했더니 에러가 생긴다.
  6. KOE010
    포스트맨에서 테스트를 하면 나오는데 이거는 진짜 모르겠다. 구글링을 통해서 포스트맨으로 로그인 토큰 발급 테스트가 가능하다는 것을 알았고 따라서 해보는데 계속 발생한다. 원인은 계속 REST API 키 혹은 카카오 로그인 보안 설정에서 Client Secret 설정된 경우 이게 틀리면 생기는 에러라고 하는데 열심히 Request Headers를 들여다봐도 모르겠다.
     마찬가지로 다시 구글링을 해서 여러 방법들이 나왔고 Content-Type이 틀릴 수도 있다고 해서  찾아봤는데 application/x-www-form-urlencoded으로 하는 게 맞았다.  Authorization: Basic OTNiNjA1MzAwNTM3M2M1NWMxNGZjZDI3YTUzZjZmMjk6 이게  client_id 부분 같아서 디코드 해서 확인해도 REST API 키와 같았다. 보안 설정은 하지 않았었다. 이후에 보안 추가해 보고 REST API 키도 재발급해봤지만 결국 해결하지 못했다. 그래서 버튼만 있는 html을 만들어서 시도했다가 우연히 로컬 호스트 주소로 갔는데 브라우저에서 자동으로 실행이 되었다. 생각해 보니 카카오 로그인 찾아볼 때 카카오 로그인이 GET 요청으로 실행된다는 것을 본 적이 있었다. 아무튼 이후엔 브라우저에서 API 주소를 입력해서 실행했다. 다행히 이때부턴 이 에러가 발생하지 않았다.
  7. Error: Failed to serialize user into session
    세션 직렬화.
    이것도 이후에 가장 많이 고생했던 부분이다. 세션 설정을 하고 카카오 로그인 시에만 세션을 사용하지 않는 session: false를 추가했는데 이후에 실행되는 callbackURL에서도 session: false를 추가해줬어야 했다. 이 사실을 알기 위해서 중간중간 콘솔을 찍어보고 왜 실행이 안 될까 하면서 코드를 수정하다가 패스포트 미들웨어를 지우니까 실행이 돼서 원인을 알았고 혹시나 해서 session: false를 추가하니 해결이 되었다. passport.authenticate 메서드 자체가 세션에 데이터를 저장하는 게 기본값이고 이를 처리해 줬어야 했는데 알지 못했고 위에서 처리했기에 밑에선 생각하지 못하고 있었다.
  8. Error: Unknown authentication strategy "kakao”
    기존에 로컬 패스포트를 구현할 때 app.js에 require('./src/passport/localStrategy') 이것을 추가해줘야 했다. 그런데 카카오 로그인에서는 콜백함수를 지원하지 않기에 require('./src/passport/localStrategy')() 이렇게 사용해서 바로 실행도 시켜야지 위에 에러가 발생하지 않았다.
// kakaoStrategy.js

const passport = require("passport");
const KakaoStrategy = require("passport-kakao").Strategy;
const CustomError = require("../utils/error");

const { Users } = require("../models");

module.exports = () => {
  passport.use(
    new KakaoStrategy(
      {
        clientID: process.env.KAKAO_ID, // 카카오 로그인에서 발급받은 REST API 키
        callbackURL: process.env.KAKAO_URL, // 카카오 로그인 Redirect URI 경로
      },
      /*
       * clientID에 카카오 앱 아이디 추가
       * callbackURL: 카카오 로그인 후 카카오가 결과를 전송해줄 URL
       * accessToken, refreshToken: 로그인 성공 후 카카오가 보내준 토큰
       * profile: 카카오가 보내준 유저 정보. profile의 정보를 바탕으로 회원가입
       */
      async (accessToken, refreshToken, profile, done) => {
        try {
          const exUser = await Users.findOne({
            // 카카오 플랫폼에서 로그인 했고 & snsId필드에 카카오 아이디가 일치할경우
            where: {
              kakaoId: profile.id,
              providerType: "kakao",
            },
            paranoid: false,
          });
          // 이미 가입된 카카오 프로필이면 성공
          if (exUser) {
            if (exUser.dataValues.deletedAt) {
              done(null, false, { message: "탈퇴한 회원입니다." });
            }
            done(null, exUser); // 로그인 인증 완료
          } else {
            // 가입되지 않는 유저면 회원가입 시키고 로그인을 시킨다
            const newUser = await Users.create({
              loginId: profile._json && profile._json.kakao_account_email,
              nickname: profile.displayName,
              kakaoId: profile.id,
              profileUrl:
                profile._json &&
                profile._json.kakao_account.profile.profile_image_url,
              providerType: "kakao",
            });
            done(null, newUser); // 회원가입하고 로그인 인증 완료
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }
    )
  );
};

 

// login.route.js

router.get("/kakao", passport.authenticate("kakao", { session: false }));
router.get(
  "/kakao/callback",
  passport.authenticate("kakao", {
    failureRedirect: "https://memorymingle.shop/login?error=KakaoLoginFailed",
    session: false,
  }),
  asyncHandler(async (req, res) => {
    const user = req.user;

    // 액세스 토큰 생성
    const accessToken = jwt.sign(
      { userId: user.userId },
      process.env.JWT_SECRET,
      {
        expiresIn: "15m",
      }
    );

    // 리프레시 토큰 생성
    const refreshToken = jwt.sign(
      { userId: user.userId },
      process.env.JWT_REFRESH_SECRET,
      {
        expiresIn: "7d",
      }
    );

    // 리프레시 토큰을 레디스에 삭제 저장
    await deleteRefreshToken(user.userId);
    await saveRefreshToken(user.userId, refreshToken);

    res.cookie("MM", `Bearer ${accessToken}`, {
      secure: true,
      httpOnly: true,
      sameSite: "none",
    });
    res.cookie("refreshToken", refreshToken, {
      secure: true,
      httpOnly: true,
      sameSite: "none",
    });
    res.redirect("https://memorymingle.shop/groupmain");
  })
);

module.exports = router;

이렇게 해서 JWT토큰을 생성하고 이를 사용하는 카카오 로그인은 완성을 했는데 세션을 이용하는 방법도 찾아보고 passport에 대해서도 자세히 알아보는 시간이 필요할 것 같다.

'오늘 뭐했냐 > 함께했던 작업들' 카테고리의 다른 글

23.08.21 프로젝트 중간 점검  (0) 2023.08.30
23.08.10 Error Handling  (0) 2023.08.15
23.08.07 passport-local  (0) 2023.08.12
23.08.06 쿠키 전달의 문제  (0) 2023.08.10
23.08.05 끝 없는 기획  (0) 2023.08.10