KDT/TIL

9/19 TIL : ์ž๋™ ๋กœ๊ทธ์ธ(์ฟ ํ‚ค), DOTENV, OAuth(ํŽ˜์ด์Šค๋ถ, ๋„ค์ด๋ฒ„, ์นด์นด์˜ค, ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ)

ebulsok 2022. 9. 20. 20:31

๐Ÿ”Ž ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•œ ์ž๋™ ๋กœ๊ทธ์ธ

  • ๋กœ๊ทธ์ธ ํ•˜๋ฉด ์ฟ ํ‚ค ๋ฐœํ–‰(์‚ฌ์šฉ์ž id ์ •๋„, 60์ดˆ์˜ expires ์„ค์ •, httpOnly ์˜ต์…˜ ์ผœ๊ธฐ, signed ์˜ต์…˜ ์ผœ๊ธฐ+์„œ๋ฒ„์˜ cookie-parser์— ์•”ํ˜ธํ™” ํ‚ค ์„ค์ •)
router.post('/', (req, res, next) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) throw err;
    if (!user) {
      return res.send(
        `${info.message}<br><a href="/login">๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™</a>`
      );
    }
    req.logIn(user, (err) => {
      if (err) throw err;
      res.cookie('user', req.body.id, {
        expires: new Date(Date.now() + 1000 * 60),
        httpOnly: true,
        signed: true,
      });
      res.redirect('/board');
    });
  })(req, res, next);
});
// app.js
app.use(cookieParser('ebulsok'));

 

  • ๋กœ๊ทธ์ธ์ด ์•ˆ ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ express session, passport session ๋‘˜ ๋‹ค ์—†๋Š” ์ƒํƒœ
  • req.user๊ฐ€ undefined ์ƒํƒœ์ธ๋ฐ ๊ฑฐ๊ธฐ์„œ ํ‚ค ๊ฐ’์„ ์ฐธ์กฐํ•˜๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜ ๋ฐœ์ƒ
  • req.user๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์— ํŠน์ • ํ‚ค ๊ฐ’์„ ์ฐธ์กฐํ•˜๋„๋ก board.js ์ฝ”๋“œ ์ˆ˜์ •
router.get('/', isLogin, async (req, res) => {
  const client = await mongoClient.connect();
  const cursor = client.db('KDT-1').collection('board');
  const ARTICLE = await cursor.find({}).toArray();

  const articleLen = ARTICLE.length;
  res.render('board', {
    ARTICLE,
    articleCounts: articleLen,
    userID: req.session.userID
      ? req.session.userID
      : req.user?.id
      ? req.user?.id
      : req.signedCookies.user,
  });
});

(session์€ express ๋ชจ๋“ˆ์„ ์‹คํ–‰์‹œํ‚ฌ ๋•Œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์— ?์„ ์ถ”๊ฐ€ํ•  ํ•„์š” ์—†์Œ)

 

๐Ÿ”Ž isLogin ํ•จ์ˆ˜ ๋ชจ๋“ˆํ™”

  • isLogin ํ•จ์ˆ˜๋ฅผ login.js์— ์ด๋™
  • ํ•จ์ˆ˜๋ฅผ ๋ชจ๋“ˆ๋กœ ๋บ„ ์ˆ˜ ์žˆ๊ฒŒ ์ต๋ช… ํ•จ์ˆ˜ ์„ ์–ธ
  • isLogin์„ module.exports์— ์ถ”๊ฐ€
const isLogin = (req, res, next) => {
  if (req.session.login || req.user || req.signedCookies.user) next();
  else {
    res.status(300);
    res.send(
      '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.<br><a href="/login">๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™</a>'
    );
  }
};

module.exports = { router, isLogin };

 

  • login.js ๋ชจ๋“ˆ์€ ํ•˜๋‚˜์˜ router๊ฐ€ ์•„๋‹Œ isLogin์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— app.js ์ฝ”๋“œ ์ˆ˜์ • ํ•„์š”
app.use('/login', loginRouter.router);
  • board.js์—์„œ ๋ชจ๋“ˆ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
  • ๊ธฐ์กด์˜ isLogin์„ login.isLogin์œผ๋กœ ๋ณ€๊ฒฝ
const login = require('./login');

// ๊ธ€ ๋ชฉ๋ก
router.get('/', login.isLogin, async (req, res) => { ... });

// ๊ธ€ ๋“ฑ๋ก
router.get('/write', login.isLogin, (req, res) => { ... });

router.post('/write', login.isLogin, async (req, res) => { ... });

// ๊ธ€ ์ˆ˜์ •
router.get('/edit/postID/:postID', login.isLogin, async (req, res) => { ... });

// ๊ธ€ ์‚ญ์ œ
router.delete('/delete/postID/:postID', login.isLogin, async (req, res) => { ... });

 

๐Ÿ”Ž DOTENV

์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์™ธ๋ถ€ ์ฝ”๋“œ์—์„œ ํ™•์ธ์ด ๋ถˆ๊ฐ€๋“ฑํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ๋ชจ๋“ˆ

  • [ํ„ฐ๋ฏธ๋„] npm i dotenv -s
  • app.js์—์„œ ๋ชจ๋“ˆ ํ˜ธ์ถœํ•˜๊ธฐ
require('dotenv').config();
  • .env ํŒŒ์ผ์„ ์ตœ์ƒ๋‹จ ํด๋”์— ๋งŒ๋“ค๊ธฐ
PORT = 4000
DB_URI = mongodb+srv://ebulsok:<password>@cluster0.mhxf9lp.mongodb.net/?retryWrites=true&w=majority
  • ํ•„์š”ํ•œ ๊ณณ์—์„œ process.env.์ €์žฅ๋ช… ์œผ๋กœ ์‚ฌ์šฉ
// app.js
const PORT = process.env.PORT;

// mongo.js
const uri = process.env.DB_URI;
  • .gitignore์— ์ถ”๊ฐ€

 

๐Ÿ”Ž OAuth(Open Authorization)

  • ์ธํ„ฐ๋„ท ์‚ฌ์šฉ์ž๋“ค์ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์›น์‚ฌ์ดํŠธ ์ƒ์˜ ์ž์‹ ๋“ค์˜ ์ •๋ณด์— ๋Œ€ํ•ด ์›น์‚ฌ์ดํŠธ๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•œ ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€
  • ๋ณดํ†ต ์‚ฌ์šฉํ•˜๋Š” sns ๋กœ๊ทธ์ธ!

 

๐Ÿšฉ 3 ์ฐธ์—ฌ์ž

  • resource server: ์ธ์ฆ์— ๋Œ€ํ•œ ์ž์›์„ ์‹ค์ œ๋กœ ๋ณด์œ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ธ์ฆ์„ ์‹ค์ œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ์„œ๋ฒ„(ex. ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€ ...)
  • resource owner: ์ธ์ฆ ์ •๋ณด์˜ ์ฃผ์ธ, ์ฆ‰ ์‚ฌ์šฉ์ž
  • client: resource server์—์„œ ์ž์›์„ ๊ฐ€์ ธ์˜ค๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘์†ํ•˜๋ ค๋Š” ์„œ๋น„์Šค

 

๐Ÿšฉ OAuth Flow

  1. resource server์— client ๋“ฑ๋ก
  2. resource server์˜ ๊ฐ€์ด๋“œ์— ๋”ฐ๋ผ ์ธ์ฆ ๊ตฌํ˜„
  3. ์‚ฌ์šฉ์ž๊ฐ€ client์—์„œ ์ธ์ฆ ์š”์ฒญ
  4. resource server์—์„œ ์ธ์ฆ ํ† ํฐ ๋ฐœํ–‰
  5. ์‚ฌ์šฉ์ž๊ฐ€ resource server์— ๋กœ๊ทธ์ธ
  6. resource server๊ฐ€ ์ธ์ฆ ํ† ํฐ ๊ฒ€์ฆ
  7. ๋กœ๊ทธ์ธ ์™„๋ฃŒ

 

๐Ÿšฉ Facebook ๋กœ๊ทธ์ธ ๊ตฌํ˜„(https://developers.facebook.com/)

  • ์•ฑ ๋งŒ๋“ค๊ธฐ - ์†Œ๋น„์ž - ์•ฑ์— ์ œํ’ˆ ์ถ”๊ฐ€(facebook ๋กœ๊ทธ์ธ) - ์›น - ์‚ฌ์ดํŠธ url ์ž…๋ ฅ(http://localhost:4000/)
  • ์„ค์ •- ๊ธฐ๋ณธ ์„ค์ • - URL ์ž…๋ ฅ ๋ž€์— ์„œ๋ฒ„ ์ž‘๋™๋˜๋Š” ๋งํฌ ๊ฑธ๊ธฐ
  • ์•ฑ ๊ฒ€์ˆ˜ - ๊ถŒํ•œ ๋ฐ ๊ธฐ๋Šฅ - public_profile - ๊ณ ๊ธ‰ ์•ก์„ธ์Šค ์ด์šฉํ•˜๊ธฐ
  • ๋ชจ๋“ˆ ์„ค์น˜: [ํ„ฐ๋ฏธ๋„] npm i passport-facebook -s
  • .env์— ์ธ์ฆ ์ •๋ณด ์ถ”๊ฐ€
FB_CLIENT = 
FB_CLIENT_SECRET =
FB_CB_URL =
  • ๋ชจ๋“ˆ ์ถ”๊ฐ€, ์ „๋žต ์ˆ˜๋ฆฝ(facebook profile์—์„œ id ๊ฐ’์ด DB์— ์žˆ๋‚˜ ์ฐพ์•„๋ณด๊ณ  ๋กœ๊ทธ์ธ, ์—†๋‹ค๋ฉด ํšŒ์›๊ฐ€์ž… ํ›„ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ)
// passport.js
const FacebookStrategy = require('passport-facebook').Strategy;

module.exports = () => {
  passport.use(
    new LocalStrategy( ...)
  );

  passport.use(
    new FacebookStrategy(
      {
        clientID: process.env.FB_CLIENT,
        clientSecret: process.env.FB_CLIENT_SECRET,
        callbackURL: process.env.FB_CB_URL,
      },
      async (accessToken, refreshToken, profile, cb) => {
        // console.log(profile);
        const client = await mongoClient.connect();
        const userCursor = client.db('KDT-1').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.displayName
              : 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) => { ... });

  passport.deserializeUser(async (id, cb) => { ... });
};
  • ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ(์„ฑ๊ณต ์‹œ ๊ฒŒ์‹œํŒ์œผ๋กœ, ์‹คํŒจ ์‹œ ๋ฉ”์ธํ™”๋ฉด์œผ๋กœ)
// login.js
router.get(
  '/auth/facebook',
  passport.authenticate('facebook', { scope: 'email' })
);

router.get(
  '/auth/facebook/callback',
  passport.authenticate('facebook', {
    successRedirect: '/board',
    failureRedirect: '/',
  })
);

 

  • ๊ธ€์„ ์“ธ ๋•Œ userName ํ‚ค๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก borad.js ์ˆ˜์ •
// ๊ธ€ ์“ฐ๊ธฐ
const newArticle = {
      title: req.body.title,
      content: req.body.content,
      userID: req.session.userID
        ? req.session.userID
        : req.user.id
        ? req.user.id
        : req.signedCookies.user,
      postID: postID,
      userName: req.user?.name ? req.user.name : req.user?.id,
};
  • ์ž‘์„ฑ์ž ๋ถ€๋ถ„์—์„œ ํ•ด๋‹น ๊ธ€์ด userName ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด userName, ์—†์œผ๋ฉด userID๋ฅผ ๋„์šฐ๋„๋ก board.ejs ์ˆ˜์ •
<p>์ž‘์„ฑ์ž: <%= ARTICLE[i].userName ? ARTICLE[i].userName : ARTICLE[i].userID %></p>

 

๐Ÿšฉ ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ๊ตฌํ˜„(https://developers.naver.com/)

  • ๋ชจ๋“ˆ ์„ค์น˜: [ํ„ฐ๋ฏธ๋„] npm i passport-naver -s
  • .env์— ์ธ์ฆ ์ •๋ณด ์ถ”๊ฐ€
NV_CLIENT =
NV_CLIENT_SECRET =
NV_CB_URL =
  • ๋ชจ๋“ˆ ์ถ”๊ฐ€, ์ „๋žต ์ˆ˜๋ฆฝ
// passport.js
const NaverStrategy = require('passport-naver').Strategy;

	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('KDT-1').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.displayName
                  : profile.emails[0].value,
                provider: profile.provider,
              };

              const dbResult = await userCursor.insertOne(newUser);
              if (dbResult.acknowledged) cb(null, newUser);
              else cb(null, false, { message: 'ํšŒ์› ์ƒ์„ฑ์„ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.' });
            }
          }
        )
	);
  • ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
// login.js
router.get('/auth/naver', passport.authenticate('naver'));

router.get(
  '/auth/naver/callback',
  passport.authenticate('naver', {
    successRedirect: '/board',
    failureRedirect: '/',
  })
);

 

๐Ÿšฉ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๊ตฌํ˜„(https://developers.kakao.com/docs/latest/ko/kakaologin/common)

์ฐธ๊ณ : passport-kakao (passportjs.org)

  • ๋ชจ๋“ˆ ์„ค์น˜: [ํ„ฐ๋ฏธ๋„] npm i passport-kakao -s
  • .env์— ์ธ์ฆ ์ •๋ณด ์ถ”๊ฐ€
KA_CLIENT =
KA_CB_URL =
  • ๋ชจ๋“ˆ ์ถ”๊ฐ€, ์ „๋žต ์ˆ˜๋ฆฝ
// passport.js
const KakaoStrategy = require('passport-kakao').Strategy;

	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('KDT-1').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.displayName
                  : profile.emails[0].value,
                provider: profile.provider,
              };

              const dbResult = await userCursor.insertOne(newUser);
              if (dbResult.acknowledged) cb(null, newUser);
              else cb(null, false, { message: 'ํšŒ์› ์ƒ์„ฑ์„ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.' });
            }
          }
        )
      );
  • ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
// login.js
router.get('/auth/kakao', passport.authenticate('kakao'));

router.get(
  '/auth/kakao/callback',
  passport.authenticate('kakao', {
    successRedirect: '/board',
    failureRedirect: '/',
  })
);

 

๐Ÿšฉ ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๊ตฌํ˜„(https://console.cloud.google.com/)]

  • API ๋ฐ ์„œ๋น„์Šค - OAuth ๋™์˜ ํ™”๋ฉด - ์™ธ๋ถ€ - ๋งŒ๋“ค๊ธฐ - ๋งํฌ ์ž…๋ ฅ - ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž ์ถ”๊ฐ€
  • ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด - OAuth ํด๋ผ์ด์–ธํŠธ ID - ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ - ๋งํฌ ์ž…๋ ฅ - ํด๋ผ์ด์–ธํŠธ ID/PW ๋ณต์‚ฌ
  • ๋ชจ๋“ˆ ์„ค์น˜: [ํ„ฐ๋ฏธ๋„] npm i passport-google-oauth20
  • .env์— ์ธ์ฆ ์ •๋ณด ์ถ”๊ฐ€
GG_CLIENT = 
GG_CLIENT_SECRET = 
GG_CB_URL =
  • ๋ชจ๋“ˆ ์ถ”๊ฐ€, ์ „๋žต ์ˆ˜๋ฆฝ
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('KDT-1').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.displayName
              : profile.emails[0].value,
            provider: profile.provider,
          };

          const dbResult = await userCursor.insertOne(newUser);
          if (dbResult.acknowledged) cb(null, newUser);
          else cb(null, false, { message: 'ํšŒ์› ์ƒ์„ฑ์„ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.' });
        }
      }
    )
  );
  • ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
router.get('/auth/google', passport.authenticate('google', { scope: 'email' }));

router.get(
  '/auth/google/callback',
  passport.authenticate('google', {
    successRedirect: '/board',
    failureRedirect: '/',
  })
);

๐Ÿ”Ž .gitignore ๊ฐ€ ์•ˆ๋˜๋Š” ๊ฒฝ์šฐ: ์บ์‹œ ์‚ญ์ œํ•˜๊ธฐ

git rm -r --cached .
git add .
git commit -m "clear git cache"
git push --all