KDT/project

[MarkYour2022/backend] 2. 로그인(네이버, 카카오, 구글)

ebulsok 2022. 10. 22. 01:10

가장 중요한 것은 callback url을 api 설정창과 백엔드 코드에서 똑같이 입력하는 것

app.js(추가 코드만)

const session = require('express-session');
const passport = require('passport');

const loginRouter = require('./routes/login');
const passportRouter = require('./routes/passport');

passportRouter();

app.use(
  session({
    secret: 'secret key',
    resave: false,
    saveUninitialized: true,
  })
);
app.use(passport.initialize());
app.use(passport.session());

app.use('/auth', loginRouter);


routes/login.js

// @ts-check
const express = require('express');

const router = express.Router();
const passport = require('passport');

// NAVER 로그인
router.get('/naver', passport.authenticate('naver'));
router.get(
  '/naver/callback',
  passport.authenticate('naver', {
    successRedirect: '/',
    failureRedirect: '/fail',
  })
);

// KAKAO 로그인
router.get('/kakao', passport.authenticate('kakao'));
router.get(
  '/kakao/callback',
  passport.authenticate('kakao', {
    successRedirect: '/',
    failureRedirect: '/fail',
  })
);

// GOOGLE 로그인
router.get('/google', passport.authenticate('google', { scope: 'email' }));
router.get(
  '/google/callback',
  passport.authenticate('google', {
    successRedirect: '/',
    failureRedirect: '/fail',
  })
);

// 로그아웃
router.get('/logout', (req, res, next) => {
  req.logout((err) => {
    if (err) return next(err);
    return res.send('로그아웃 했습니다.<br><a href="/">HOME</a>');
  });
});

module.exports = router;
  • 회원가입을 따로 구현하면 유효성 검사 등 기술적인 측면에서 좋을 수는 있으나, 사용자 입장에서 우리 사이트를 이용할 때 회원가입을 하기 보다는 간편로그인을 이용할 확률이 훨씬 높을 것 같아서 빼기로 했다.


routes/passport.js

const passport = require('passport');
const NaverStrategy = require('passport-naver').Strategy;
const KakaoStrategy = require('passport-kakao').Strategy;
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const mongoClient = require('./mongo');

module.exports = () => {
  // NAVER 로그인
  passport.use(
    new NaverStrategy(
      {
        clientID: process.env.NV_CLIENT,
        clientSecret: process.env.NV_CLIENT_SECRET,
        callbackURL: process.env.NV_CB_URL,
      },
      async (accessToken, refreshToken, profile, cb) => {
        // console.log(profile);
        const client = await mongoClient.connect();
        const userCursor = client.db('My2022').collection('users');

        const result = await userCursor.findOne({ id: profile.id });
        if (result !== null) cb(null, result);
        else {
          const newUser = {
            id: profile.id,
            name: profile.emails[0].value,
            provider: profile.provider,
          };

          const dbResult = await userCursor.insertOne(newUser);
          if (dbResult.acknowledged) cb(null, newUser);
          else cb(null, false, { message: '회원 생성을 실패하였습니다.' });
        }
      }
    )
  );

  // KAKAO 로그인
  passport.use(
    new KakaoStrategy(
      {
        clientID: process.env.KA_CLIENT,
        callbackURL: process.env.KA_CB_URL,
      },
      async (accessToken, refreshToken, profile, cb) => {
        // console.log(profile);
        const client = await mongoClient.connect();
        const userCursor = client.db('My2022').collection('users');

        const result = await userCursor.findOne({ id: profile.id });
        if (result !== null) cb(null, result);
        else {
          const newUser = {
            id: profile.id,
            name: profile.displayName || profile.username,
            provider: profile.provider,
          };

          const dbResult = await userCursor.insertOne(newUser);
          if (dbResult.acknowledged) cb(null, newUser);
          else cb(null, false, { message: '회원 생성을 실패하였습니다.' });
        }
      }
    )
  );

  // GOOGLE 로그인
  passport.use(
    new GoogleStrategy(
      {
        clientID: process.env.GG_CLIENT,
        clientSecret: process.env.GG_CLIENT_SECRET,
        callbackURL: process.env.GG_CB_URL,
      },
      async (accessToken, refreshToken, profile, cb) => {
        // console.log(profile);
        const client = await mongoClient.connect();
        const userCursor = client.db('My2022').collection('users');

        const result = await userCursor.findOne({ id: profile.id });
        if (result !== null) cb(null, result);
        else {
          const newUser = {
            id: profile.id,
            name: profile.emails[0].value,
            provider: profile.provider,
          };

          const dbResult = await userCursor.insertOne(newUser);
          if (dbResult.acknowledged) cb(null, newUser);
          else cb(null, false, { message: '회원 생성을 실패하였습니다.' });
        }
      }
    )
  );

  passport.serializeUser((user, cb) => {
    cb(null, user.id);
  });

  passport.deserializeUser(async (id, cb) => {
    const client = await mongoClient.connect();
    const userCursor = client.db('My2022').collection('users');
    const result = await userCursor.findOne({ id });
    if (result) cb(null, result);
  });
};
  • 이 파일에서는 // @ts-check를 하지 않는 것이 좋다.. '이 호출과 일치하는 오버로드가 없습니다'라며 빨간줄이 죽죽 그어진다. 오류인 줄 알았는데 분명 코드도 알맞게 짰고 실행은 잘 되길래 뭔가 싶었다..
  • 간편로그인은 닉네임을 사용자로부터 입력받을 수 없다는 점이 불편한 것 같다. 우리 사이트는 글을 쓸 때 닉네임을 입력받으면 돼서 계정 생성 시 그럴 필요는 없지만, 로그인했다는 것을 보여주기 위해 name에 email(네이버, 구글)이나 이름(카카오)를 저장했다. 카카오 api에서 이메일을 필수 수집하려면 무슨 사업자 등록을 해야 하고 비즈계정으로 바꿔야되고 어쩌구저쩌구 한다길래 일단 필수로 받는 displayName을 저장하는 걸로 타협했다.


routes/index.js

router.get('/', (req, res) => {
  const name = req.user?.name || '로그인 필요';
  res.render('index', { name });
});

router.get('/fail', (req, res) => {
  res.render('fail');
});
  • 로그인하고 req.user에 정보가 제대로 전달되었는지 확인하기 위해 index.ejs에 name을 객체에 담아서 전달했다.


views/index.ejs

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>Back</h1>
  <h2>사용자: <%= name %></h2>
  <h2 class="mt-5 text-center"><a href="/auth/naver">네이버 로그인</a></h2>
  <h2 class="mt-5 text-center"><a href="/auth/kakao">카카오 로그인</a></h2>
  <h2 class="mt-5 text-center"><a href="/auth/google">구글 로그인</a></h2>
  <a href="/auth/logout">로그아웃</a>
</body>

</html>


views/fail.ejs

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>fail</h1>
</body>

</html>
  • 이건 임시파일


아직까진 수월하다! 아무래도 수업에서 쓴 코드를 재활용했기 때문에...
근데 나중에 React랑 연결하게 되면 redirect URL 등등 코드를 변경해야될 것 같다. 구글링하니까 무슨 토큰도 발급하고 그런다네.. 여기서 끝이 아니네...😥
https://han-py.tistory.com/417

[React/Nodejs] 카카오 로그인 연결하기

카카오 로그인을 진행해 보자. 기본적인 작동 방식은 여기를 눌러서 확인하자. 기본 셋팅은 할수 있다 판단하고 글을 적어보겠다. 사실 쉽게 사용하기 위해 만들어진 라이브러리도 많다. 그러나

han-py.tistory.com

아 그리고 배포한다는 가정하에 나중에 네이버카카오구글 전부 로그인 제한 풀어줘야 되고.. 간편로그인보다 회원가입 구현하는 게 더 수월했을지도..ㅎㅎ