본문 바로가기

Node.js

3. passport [express]

로그인 기능만 제대로 구현할 수 있어도,
백엔드의 9할은 끝난 것이다.
- 취준생 삼다수🤔 -

 

0. 세팅

  1. npm init
  2. npm i sequelize mysql2 sequelize-cli
    • mysql2는 노드와 mysql DB 연결을 위한 드라이버 (mysql X)
  3. npx sequelize init 
    • config, migrations, models, seeders 파일 자동 생성
  4. 파일 생성 - public, .env, views, routes, controllers, passport 파일 수동 생성
  5. npm i express cookie-parser express-session morgan multer dotenv nonjucks
  6. npm i -D nodemon
  7. app.js 제작
  8. scripts에 "start" : "nodemon app.js", 추가

계층적 호출이 대세이다.

app에서 라우터를 호출하고, 라우터는 컨트롤러를 호출하고, 컨트롤러는 서비스를 호출한다.

 

시퀄라이즈 ORM을 사용하여 JS로 SQL을 다룰 수 있다.

(ORM에는 한계가 있기 때문에 결국 SQL을 배워야 한다.)

 

 

1. passport

 

인증을 위한 node의 모듈이다.

세션쿠키 처리 등 복잡한 작업을 대신해준다.

 

passport는 다양한 인증방법을 제공하는데 이를

strategies(전략)이라고 한다.

 

stratege는 두가지로 구분된다.

a. local strategy : 로컬 DB를 이용한 로그인 인증하는 방식

b. social authentication : 소셜네트워크를 이용한 로그인 인증하는 방식

 

2. 설치

 

npm i passport passport-local passport-kakao bcrypt

 

bcrypt는 비밀번호를 해시를 이용해 암호화 해주는 모듈이다. (일방향, 복호화 불가)

 

3. 설정

 

a. 필요 미들웨어 설정

// app.js

const passport = require("passport");

const passportConfig = require("./passport");

passportConfig(); // 패스포트 설정

// - 세션 -

// 아래 미들웨어는 세션설정 하단에 작성
app.use(passport.initialize()); // req.user, req.login, req.isAuthenticate, req.logout 생성
app.use(passport.session()); // connect.sid라는 이름으로 세션 쿠키가 브라우저로 전송

 

b. 필요 passport 설정

// passport/index.js

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

module.exports = () => {
  // 로그인이 성공했을때 유저정보를 세션에 저장
  // connect.sid=`user.id` 전달된다.
  passport.serializeUser((user, done) => {
    console.log("serialize");
    done(null, user.id);
  });
  // 세션에 있는 사용자의 식별자를 받아서 DB에서 조회
  passport.deserializeUser((id, done) => {
    console.log("deserialize");
    User.findOne({
      where: { id },
      include: [
        {
          model: User,
          attributes: ["id", "nick"],
          as: "Followers",
        },
        {
          model: User,
          attributes: ["id", "nick"],
          as: "Followings",
        },
      ],
    })
      .then((user) => {
        console.log("user", user);
        // 성공했다
        done(null, user);
      })
      // 오류났다
      .catch((err) => done(err));
  });

  local();
};
// passport/localStrategy.js

const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");

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

module.exports = () => {
  // passport에 로컬스토리지를 등록하는 작성
  // 이메일 로그인 했을때, 어떻게 할지
  // 로그인을 시켜줘도 되는지 안되는지를 판단하는 코드
  passport.use(
    new LocalStrategy(
      {
        // usernameField passwordField passReqToCallback 이거 약속된 키이다.
        // req.body.email
        usernameField: "email",
        // req.body.password
        passwordField: "password",
        passReqToCallback: false,
      },
      async (email, password, done) => {
        try {
          const exUser = await User.findOne({ where: { email } });
          if (exUser) {
            const result = await bcrypt.compare(password, exUser.password);
            if (result) {
              // 값이 유효할 경우, 두번째 유효한 값
              done(null, exUser);
            } else {
              // 값이 유효하지 않을 경우, 세번째 오류메세지
              done(null, false, { message: "비밀번호가 일치하지 않습니다." });
            }
          } else {
            done(null, false, { message: "가입되지 않은 회원입니다." });
          }
        } catch (error) {
          console.error(error);
          // 에러 발생한 경우
          done(error);
        }
      }
    )
  );
};

 

c. 필요 middleware 설정

// middlewares/index.js


// 미들웨어를 통해서 로그인이 되어 있는지 확인
// 로그인 확인은 req.isAuthenticated()를 통해서 확인

// 로그인이 되어있어야 하는 경우를 위한 미들웨어
// /profile ...
exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send("로그인 필요");
  }
};

// 로그인이 "안"되어있어야 하는 경우를 위한 미들웨어
// /auth/login ...
exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    const message = encodeURIComponent("로그인한 상태입니다.");
    res.redirect(`/?error=${message}`);
  }
};

 

4-1. 가입(join) 흐름

 

가입에는 passport가 사용되지 않는다.

passport는 오로지 인증에 관한 모듈이다.

// controllers/auth.js

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

// 가입 (이건 passport를 상관이 전혀 없다.)
// 이 가입 코드는, 회원가입만 도와줌, 바로 로그인 시켜주지는 않음
exports.join = async (req, res, next) => {
  // req의 body에서 데이터 가져오기
  const { email, nick, password } = req.body;
  try {
    // validation
    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);
  }
};

 

 

4-2. 로그인(login) "시도" 요청 흐름

  1. /auth/login 으로 요청 입장
  2. 라우터에서 passport.authenticate 호출
  3. authenticate의 첫번째 인수에 따라서 로그인 전략 수행("local"이면, LocalStrategy)
  4. 로그인 성공시 사용자 정보 객체와 함께 req.login호출
  5. req.session에 사용자 아이디만 저장해서 세션 생성
    • 무거워짐을 방식하고자
  6. express-session 설정을 기반으로 브라우저에 connect.sid세션쿠키 전송
    • app.use(passport.session()); 에 의해
    • 브라우저는 connect.sid="~" 형태의 쿠키를 받는다.
  7. 로그인 완료
// controllers/auth.js

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

// POST /auth/login
// 로그인 (받아온 데이터와 가지고 있는 데이터를 비교)
// 이게 passport랑 상관이 있어지는 거다. 인증!의 과정이니까
exports.login = (req, res, next) => {
  // 인증을 위한 미들웨어
  // 중요중요중요!!!! local만나면 등록해두었던 LocalStrategy가 실행된다.
  passport.authenticate("local", (authError, user, info) => {
    if (authError) {
      // 서버실패
      console.error(authError);
      return next(authError);
    }
    if (!user) {
      // 로직실패
      return res.redirect(`/?error=${info.message}`);
    }
    return req.login(user, (loginError) => {
      // 로그인 성공
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      return res.redirect("/");
    });
  })(req, res, next); // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙입니다.
};

 

 

4-3. 로그인 "성공 후" 요청 흐름

  1. 요청 입장
  2. passport.session 미들웨어가 passport.deserializeUser 호출
  3. connect.sid 세션 쿠키 읽고, 세션 객체 찾은 뒤, req.session 제작
  4. req.session에 저장된 아이디를 기반으로 DB 조회
  5. DB에서 조회한 정보를 req.user에 저장
  6. 라우터에서 req.user 객체 사용가능 상태

'Node.js' 카테고리의 다른 글

5. 테스트 (jest, supertest) [express]  (1) 2024.12.09
4. 웹 API 서버 [express]  (0) 2024.12.09
2. express (서버제작 level 2) [express]  (0) 2024.11.11
1. http (서버제작 level 1) [express]  (1) 2024.11.05
3. 노드 기본 [Node]  (8) 2024.11.05