passport란 js에서 user auth에 관한 부분을 매우 간편하게 해줄 수 있는 npm 라이브러리다.
프로젝트에서 간단히 적용해보는 실습을 해보자!
🌱 passport-local-mongoose
passport local, 즉 다른 SNS(google, facebook, twitter 등) 아이디 없이 그냥 우리의 홈페이지에서 아이디 비밀번호를 설정하여 auth를 실행할 유저를 위한 auth이다. mongoose setting에서 사용하기 위해 나왔다!
아래 과정은 기본적으로 위 깃헙에서 소개한 방법을 바탕으로 한 것이니 항상 개발자 마인드로 공식문서를 참고하는 습관을 들이도록 하자.
일단 문서에서 명령한대로 다음 커맨드를 쳐서 passport를 프로젝트에 적용시킨다.
$ npm i passport passport-local passport-local-mongoose
🌱 Creating User Model
model/user.js 파일에 다음과 같은 코드를 추가한다.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const passportLocalMongoose = require("passport-local-mongoose");
const User = new Schema({
email: {
type: String,
required: true,
unique: true,
},
});
// 플러그인을 더해줌으로서 passport가 제공하는 기능(password 등)이 setting된다.
User.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", User);
User.plugin()을 통해 여러 기능들이 제공된다. ID가 unique해야한다든가, 비밀번호의 Hash 기능이라든가, passport에서 user model에게 기본적으로 기대하는 모든 method, property등이 이 과정에서 추가되게 된다.
참고로 추후에 내가 헷갈렸던 사항인데, passport는 login/logout/register method에서 "username"과 "password"를 찾는다. 그러니까 form에서 괜히 email 이런걸로 passport object에게 보내주지 말자 ㅜ credential error 뜬다.
🌱 Configuring
모델 셋팅은 완료했으니 app.js에서 configuration을 해보자. 잘 돌아가고있는 app.js 코드에 아래 require과 use를 추가한다.
const passport = require("passport");
const LocalStrategy = require("passport-local");
const User = require("./models/user");
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
LocalStrategy는 passport-local을 사용해서 user authenticate하겠다는 passport-local 모듈을 사용하는 클래스라고 보면 된다.
User.authenticate(), User.serializeUser(), User.deserializeUser()은 User model file에서 우리가 plug in 한 결과로 생겨난 static method들이다. 각각의 자세한 기능이 궁금하다면 공식 문서로!
🌱 Routing
본격적인 코드 실습이다~ router file에 유저 로그인과 관련된 register, login, logout 관련 라우팅을 할 것이다.
일단 isLoggedIn middleware이다. 유저가 로그인해야만 들어갈 수 있는 페이지에 들어가는것을 막기 위해 추가한 미들웨어이다. req.isAuthenticated()라는 passport 제공 method를 통해 로그인 여부를 판단하고, 그렇지 않다면 기존에 시도했던 url을 세션에 저장한 뒤 login form으로 향하도록 한다.
const isLoggedIn = (req, res, next) => {
// console.log("REQ.USER filled by passport:", req.user);
if (!req.isAuthenticated()) {
// 로그인 안해서 login page로 sending됐다면 로그인 이후에 그 전에 갔던 path로 돌아가는 과정이 필요.
// routes의 users.js에서 post login을 봐라!
req.session.returnTo = req.originalUrl;
req.flash("error", "You must be logged in!");
return res.redirect("/login");
}
next();
};
module.exports = isLoggedIn;
본격적인 라우팅 파일이다!
const express = require("express");
const router = express.Router();
const passport = require("passport");
const catchAsync = require("../utils/catchAsync");
const User = require("../models/user");
const isLoggedIn = require("../middleware");
router.get("/register", (req, res) => {
// register form
// req.body.email, req.body.username, req.body.password
res.render("users/register");
});
router.post(
"/register",
catchAsync(async (req, res, next) => {
const { email, username, password } = req.body;
try {
// User.register(<userobject>,<password>) : passport plugin에 들어있는 method.
// 유저 오브젝트 정보 + hashed Password를 저장한다
const newUser = await User.register(
new User({ email, username }),
password
);
req.login(newUser, (err) => {
// 또한 passport에게 보내줄 login method.
if (err) {
return next(error);
} else {
req.flash("success", "Welcome to Campground!");
res.redirect("/campgrounds");
}
});
} catch (e) {
req.flash("error", e.message);
res.redirect("/register");
}
})
);
router.get("/login", (req, res) => {
// req.body.username, req.body.password
res.render("users/login");
});
// passport가 주는 MiddleWare : passport.authenticate(<strategy>,<option>)
router.post(
"/login",
passport.authenticate("local", {
failureFlash: true,
failureRedirect: "/login",
}),
(req, res, next) => {
// 만약 어떤 이유로 isLoggedin에 걸려서 redirect 된 상황이라면!
const redirectUrl = req.session.returnTo || "/campgrounds";
delete req.session.returnTo;
req.flash("success", "Welcome back!");
res.redirect(redirectUrl);
}
);
// logout!
router.get(
"/logout",
isLoggedIn,
catchAsync(async (req, res, next) => {
// passport에서 logout은 존내 간단함~
req.logout();
req.flash("success", "Goodbye!");
res.redirect("/campgrounds");
})
);
module.exports = router;
혹시나 해서 register.ejs만 간단하게.. (어떤 식으로 백엔드에 데이터를 보냈는지 한 번은 기록하는게 좋을 것 같아서)
<%layout('layouts/boilerplate')%>
<div class="row">
<h1 class="text-center">Register</h1>
<div class="col-6 offset-3">
<form action="/register" method="POST" novalidate class="validated-form">
<div class="mb-3">
<label for="username" class="form-label">Username: </label>
<input
class="form-control"
type="text"
id="username"
name="username"
required
/>
<div class="valid-feedback">Looks Good!</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-mail: </label>
<input
class="form-control"
type="email"
id="email"
name="email"
required
/>
<div class="valid-feedback">Looks Good!</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password: </label>
<input
class="form-control"
type="password"
id="password"
name="password"
required
/>
<div class="valid-feedback">Looks Good!</div>
</div>
<div class="mb-3">
<button class="btn btn-primary">Register</button>
</div>
</form>
<a href="/campgrounds">Back to all campgrounds</a>
</div>
</div>