본문으로 바로가기
 

Passport.js

Simple, unobtrusive authentication for Node.js

www.passportjs.org

 

saintedlama/passport-local-mongoose

Passport-Local Mongoose is a Mongoose plugin that simplifies building username and password login with Passport - saintedlama/passport-local-mongoose

github.com

 

express-session을 통해 세션 사용하기

Middleware Quick npm i pug morgan helmet cookie-parser express-session connect-flash ejs npm i @babel/node @babel/preset-env @babel/core npm i -D nodemon 미들웨어는 req와 최종 res 사이에 존재하는 중..

darrengwon.tistory.com


passport가 제공하는 여거 strategy를 이용하여 유저 authentication을 작성할 수 있다. facebook, google, github, local(홈페이지에서 직접 개인정보 관리)... 소규모 사업에서는 local 보다는 federated한 social login을 사용하기를 권장한다.

 

* 소셜 로그인에는 비밀번호가 없습니다.

* OAuth2.0을 공부하면 passport.js의 원리를 이해할 수 있습니다.

 

 

🔑 설치

 

local이란 개인 정보를 받아서 서비스를 제공하는 측에서 운영하는 것을 말한다.

 

 * cookie-parser와 express-session은 미리 설치했다고 가정한다. session store를 저장하고 싶다면 위에 첨부한 게시물을 참고하자. (session store를 메모리로 설정하고 nodemon을 돌리면 정보가 휘발될 것이다.)

 

npm i passport passport-local passport-local-mongoose

 

 

 

🔑 passportlocalmongoose를 모델 plugin에 부착하기

 

기존에 작성한 User model에 passportlocalmongoose를 이용해보자.

 

passportlocalmongoose는 복잡한 함수들을 직접 만들지 않고 미리 만들어 놓은 것들의 집합이다. JWT를 이용해서 암호화라하고 이것저것 설정하는 시간을 줄일 수 있게 된다.

 

우선 모델에 plugin을 설치해주자.

옵션은 다음을 참고한다 (https://github.com/saintedlama/passport-local-mongoose#options)

[적용하고 하는 모델 스키마].plugin(passportLocalMongoose, options);

 

 

models/User.js

// User 데이터 모델입니다.

import mongoose from "mongoose";
import passportLocalMongoose from "passport-local-mongoose";

const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  avatarUrl: String,
  facebookID: Number,
  githubId: Number
});

// passportLocalMongoose 적용함.
UserSchema.plugin(passportLocalMongoose, {
  usernameField: "email"
});

const model = mongoose.model("User", UserSchema);
export default model;

 

plugin을 지정한 User 모델은 이제 passport-local-mongoose에서 제공해주는 메서드를 사용할 수 있게 되었습니다.

 

제공하는 메서드는 대략, setPassword, changePassword, authenticate, resetAttempts, createStrategy, serializeUser, deserializeUser, register, findByUsername 등 등입니다.

 

해당 메서드에 대한 자세한 내용은 여기를 참고합시다.

(https://github.com/saintedlama/passport-local-mongoose#api-documentation)

 

 

🔑 passport.js 구성하기 + serializeUser, deserializeUser

 

./passport.js에 다음과 같이 구성한다.

passport.use("strategy")를 구성하는 것이 본래 맞으나, passport-local-mongoose는 createStrategy 메서드를 제공합니다. 

import passport from "passport";
import User from "./models/User";

// strategy 생성
passport.use(User.createStrategy());

 

이제 serialize와 deserialize를 구성해보겠습니다. 위 코드에 연속해서 적을 것입니다.

 

serialize란 req.session 객체에 (그러니까 세션에) 무엇을 저장할 것인지를 선택합니다. 세션에 모든 정보를 저장하는 것은 세션의 용량을 너무 크게 만드므로 대개 user의 id만을 저장합니다.

 

deserialize는 serialize를 통해 받은 유저의 id를 이용해 이용자를 식별하는 것이다. 조회한 정보는 req.user 객체에 저장됩니다. 로그인 후에 다시 웹페이지에 접속했을 때 해당 이용자가 어떤 이용자인지 식별해내는 역할을 한다.

 

이 모든 것을 직접 구현하면 어렵지만 passport-local-mongoose 패키지에서 이미 구현해놓았으므로 사용하기만 하면 된다. (문서를 읽어보라)

 

추상화의 정도가 높아서 불안할 정도로 빠르게 완성할 수 있습니다.

import passport from "passport";
import User from "./models/User"; // passport local mongoose 적용한 User 모델을 가져왔습니다

passport.use(User.createStrategy());

passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

 

 

🔑 post 처리하기, User.register

 

로그인을 하겠다고 클라이언트 쪽에서 정보를 담아 post 날리면 처리해야겠죠.

클라이언트 단에서 폼을 작성해서 req.body에 유저 정보를 날렸다고 하면 아래 코드는 그 post를 처리하는 컨트롤러입니다. 

 

import routes from "../routers/routes";
import User from "../models/User";

export const postJoin = async (req, res, next) => {

  // 클라이언트에서 날린 정보를 추출
  const { name, email, password, password2 } = req.body;
  
  // 비밀번호가 verify
  if (password !== password2) {
    res.status(400);
    res.render("join", { pageTitle: "Join" });
  } else {
  

    try {
      // 비밀번호 verify가 되면, 해당 유저의 이름과 이메일을 가진 User 객체 생성
      // 주의하세요, User.create하면 User.regiter를 먹어버립니다
      const user = await User({ name, email });
      
      // passport-local-mongoose가준 register 메서드를 사용합니다.
      // 첫번째로 User 객체(user)를 넣고, 그 다음 인자로 비밀번호를 줍니다.
      await User.register(user, password);
      
      // 등록을 했으니 로그인 처리를 할 수 있도록 next()로 날려주자
      next();
    } catch (error) {
      console.log(error);
      res.redirect(routes.home);
    }
  }
};

 

 

 

 

 

passport-local-mongoose가 준 register 메서드의 설명은 

(https://github.com/saintedlama/passport-local-mongoose#static-methods)에 있습니다.

 

  • register(user, password, cb) Convenience method to register a new user instance with a given password. Checks if username is unique. See login example.

 

여기까지 진행하고 실제로 post를 날려보면, DB에 유저 정보가 생겼음을 확인할 수 있습니다.

주의할 점이, User.create({name, email})로 작성하면 User.register가 만들어주는 내용을 먹어버립니다.

그냥 User({name, email})로 작성합시다.

 

 

그러나 가입(register)는 되었지만 로그인이 되지 않은 상태입니다. 

 

 

 

🔑 로그인 처리하기. passport.authenticate

 

 

앞서 작성한 postJoin은 유저 가입에 성공하면, next()로 다음 미들웨어로 넘어가게끔 작성되었습니다.

globalRouter.post(routes.join, postJoin, postLogin);

 

이제 postLogin 함수를 작성해서 이제 유저 로그인을 해봅시다.

 

import passport from "passport";

export const postLogin = passport.authenticate("local", {
  successRedirect: routes.home,
  failureRedirect: routes.login,
});

 

passport.authenticate의 결과로 req.user를 반환합니다.

대략 이렇게 생겼습니다.

{
  _id: 5eed1d0e94d09823444f40aa,
  name: 'test',
  email: 'test@gmail.com',
  __v: 0
}

 

여기서 주의할 것은, 이것은 passport의 메서드이지 passport-local-mongoose의 메서드가 아니라는 것입니다. passport 공식 문서를 보면 다음과 같이 예시를 들고 있습니다.

app.post('/login', 
  passport.authenticate('google', {successRedirect: '/',
                                  failureRedirect: '/login' }));

 

 

 

🔑 app.js에 passport 연결하기

 

* passport는 session을 사용하기 때문에 exporess-session 미들웨어 코드 다음에 작성해야한다.

app.use(session()) 코드 아래에 위치해야 한다는 말이다. 또, Cookie 나 Cookie-parser 미들웨어 다음에 작성해야 한다. 

 

./app.js

// Strategy를 만든 passport.js를 import
// 전략을 찾을 수 없다는 오류는 주로 이 import를 하지 않아서 발생
import "./passport";

// 세션과 쿠키 미들웨어
app.use(cookieParser(process.env.COOKIE_ID));
app.use(
  session({
    secret: process.env.COOKIE_ID,
    resave: true,
    saveUninitialized: false
  })
);

// app.js의 middleware 처리
app.use(passport.initialize());
app.use(passport.session());

 

app.use(passport.initialize());

app.use(passport.session());

 

이걸 안해주면 세션에 저장도 안되고 passport 작동도 안하니 꼭 해줍시다.

 

위 코드를 작성한 후 로그인 처리를 마친 후에는 자동으로 쿠키에 로그인한 유저의 정보가 담깁니다.

 

 

 

 

🔑 세션 저장소를 MongoDB로 설정하기

 

express-session 에 store를 따로 지정하지 않아 메모리에 저장됨에 따라 서버를 재가동할 때마다 세션이 초기화된다.

이를 방지 하기위해 세션 저장소를 mongoDB로 해보자.

 

(https://www.npmjs.com/package/connect-mongo)

npm i conect-mongo

 

app.js

// app.js

import mongoose from "mongoose";
import session from "express-session";
const MongoStore = require("connect-mongo")(session);


app.use(
  session({
    secret: CookieSecret,
    resave: true,
    saveUninitialized: false,
    store: new MongoStore({ mongooseConnection: mongoose.connection }),
  })
);

 

store에 mongoose.connection을 쓴 것은, 기존에 연결된 DB를 그대로 사용하겠다는 말입니다.

 

이제 로그인을 해보면, DB에 로그인 세션 정보가 생긴 것을 볼 수 있다!

 

 

이제 서버를 껐다 켜도 그대로 req.user에 정보가 담겨 있음을(로그인이 되어 있음을) 확인할 수 있다!

 

 

🔑 isLoggedin/isNotLoggedin 를 통해 route protection

 

추후 해야 할 일은, 각 router마다 isLoggedin/isNotLoggedin 처리를 해주는 것이다.

예를 들어, 내가 로그인 한 상태인데 join이나 login 페이지로 이동이 가능하다면 그건 유저 경험에 좋지 못한 경험일 것이다.

 

로직은 마음대로 구현해도 된다. req.user의 값을 체크하는 방법, req.isAuthenticated를 체크하는 방법 등이 있다.

 

req.user가 존재하는지를 통해 미들웨어를 만들어 봤다.

export const onlyNotLoggedInCanView = (req, res, next) => {
  if (req.user) {
    res.redirect(routes.home);
  } else {
    next();
  }
};

export const onlyLoggedInCanView = (req, res, next) => {
  if (!req.user) {
    res.redirect(routes.home);
  } else {
    next();
  }
};

  

 

이런 식으로 사용하면 되겠죠~

globalRouter.get(routes.join, onlyNotLoggedInCanView, getJoin);
globalRouter.post(routes.join, onlyNotLoggedInCanView, postJoin, postLogin);

globalRouter.get(routes.login, onlyNotLoggedInCanView, getLogin);
globalRouter.post(routes.login, onlyNotLoggedInCanView, postLogin);

 

 

🔑 로그아웃 req.logout()

너무 간단합니다 ㅋㅋ

export const logout = (req, res) => {
  req.logout();
  res.redirect(routes.home);
};

darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체