프로그래밍 언어/NODE JS

카카오 로그인 구현하기 (1)

· 코딩마이데이

카카오 로그인이란 로그인 인증 과정을 카카오에 맡기는 것을 뜻합니다. 사용자는 번거롭게 새로운 사이트에 회원가입하지 않아도 되므로 좋고, 서비스 제공자는 로그인 과정을 검증된 SNS에 안심하고 맡길 수 있어 좋습니다.

SNS 로그인의 특징은 회원가입 절차가 따라 없다는 것입니다. 처음 로그인할 때는 회원가입 처리를 해야 하고, 두 번째 로그인부터는 로그인 처리를 해야 합니다. 따라서 SNS 로그인 전략은 로컬 로그인 전략보다 다소 복잡합니다.

const passport = require("passport");
const kakaoStrategy = require("passport-kakao").Strategy;

const User = require("../models/user");

module.exports = () => {
  passport.use(
    new kakaoStrategy(
      {
        clientId: process.env.KAKAO_ID,
        callbackURL: "/auth/kakao/callback",
      },
      async (accessToken, refreshToken, profile, done) => {
        console.log("kakao profile", profile);
        try {
          const exUser = await User.findOne({
            where: { snsId: profile.id, provider: "kakao" },
          });
          if (exUser) {
            done(null, exUser);
          } else {
            const newUser = await User.create({
              email: profile._json && profile._json.kakao_account_email,
              nick: profile.displayName,
              sosId: profile.id,
              provider: "kakao",
            });
            done(null, newUser);
          }
        } catch (error) {
          console.error(error);
          done(error);
        }
      }
    )
  );
};

 

passport-kakao 모듈로부터 Strategy 생성자를 불러와 전략을 구현합니다.

 

이제 카카오 로그인 라우터를 만들어봅시다. 로그아웃 라우터 아래에 추가하면 됩니다. 회원가입은 바로 코딩할 필요가 없고, 카카오 로그인 전략이 대부분의 로직을 처리하므로 라우터가 상대적으로 간단합니다.

const express = require("express");
const passport = require("passport");
const bcrypt = require("bcrypt");
const { isLoggedIn, isNotLoggedIn } = require("./middlewares");
const User = require("../models/user");

const router = express.Router();

router.post("/join", isNotLoggedIn, async (req, res, next) => {
  const { email, nick, password } = req.body;
  try {
    const exUser = await User.findOne({ where: { email } });
    if (exUser) {
      return res.redirect("/join?error=exist");
    }
    const hash = await bcrypt.hash(password, 12);
    await User.create({
      email,
      nick,
      password: hash,
    });
    return res.redirect("/");
  } catch (error) {
    console.error(error);
    return next(error);
  }
});

router.post("/login", isNotLoggedIn, (req, res, next) => {
  passport.authenticate("local", (authError, user, info) => {
    if (authError) {
      console.error(authError);
      return next(authError);
    }
    if (!user) {
      return res.redirect(`/?loginmError=${info.message}`);
    }
    return req.login(user, (loginError) => {
      if (loginError) {
        console.log(loginError);
        return next(loginError);
      }
      return res.redirect("/");
    });
  })(req, res, next); // 미들웨어 내의 미들웨어는 (req, res, next)을 붙입니다.
});

router.get("/logout", isLoggedIn, (req, res) => {
  req.logout();
  req.session.destroy();
  res.redirect("/");
});

router.get("/kakao", passport.authenticate("kakao"));

router.get(
  "/kakao/callback",
  passport.authenticate("kakao", {
    failureRedirect: "/",
  }),
  (req, res) => {
    res.redirect("/");
  }
);

module.exports = router;

 

GET / auth/kakao로 접근하면 카카오 로그인 과정이 시작됩니다. layout.html을 카카오톡 버튼에 /auth/kakao 링크가 붙어 있습니다. GET /auth/kakao에서 로그인 전략을 수행하는 데, 처음에는 카카오 로그인 창으로 리다이렉트합니다. 그 창에서 로그인 후 성공 여부 결과를 GET /auth/kakao/callback으로 받습니다. 이 라우터에서는 카카오 로그인 전략을 다시 수행합니다.

로컬 로그인과 다른 점은 passport.authenticate 메서드에 콜백 함수를 제공하지 않는다는 점입니다. 카카오 로그인은 로그인 성공 시 내부적으로 req.login을 호출하므로 우리가 직접 호출할 필요가 없습니다. 콜백 함수 대신 로그인에 실패했을 때 어디로 failureRedirect 속성에 적고, 성공 시에도 어디로 이동할지를 다음 미들웨어에 적습니다.

추가한 auth 라우터를 app.js에 연결합니다.

const express = require("express");
const cookieParser = require("cookie-parser");
const morgan = require("morgan");
const path = require("path");
const session = require("express-session");
const nunjucks = require("nunjucks");
const dotenv = require("dotenv");
const passport = require("passport");

dotenv.config();
const pageRouter = require("./routes/page");
const authRouter = require("./routes/auth");
const { sequelize } = require("./models");
const passportConfig = require("./passport");

const app = express();
passportConfig(); // 패스포트 설정
app.set("port", process.env.PORT || 8001);
app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
  watch: true,
});
sequelize
  .sync({ force: false })
  .then(() => {
    console.log("데이터베이스 연결 성공");
  })
  .catch((err) => {
    console.error(err);
  });

app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
  session({
    resave: false,
    saveUninitialized: false,
    secret: process.env.COOKIE_SECRET,
    cookie: {
      httpOnly: true,
      secure: false,
    },
  })
);
app.use(passport.initialize());
app.use(passport.session());

app.use("/", pageRouter);
app.use("/auth", authRouter);

app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기중");
});