JUINTINATION
Express.js와 passport-local을 사용한 로그인 테스트 본문
passport란?
이전 Express.js의 morgan과 cookie-parser, express-session 글에서 쿠키와 세션을 이용해 클라이언트를 식별하는 방법에 대해 다뤘다. passport는 내부적으로 쿠키와 세션을 사용하여 Node.js에서 인증(Authenicate)을 쉽게 적용할 수 있게 하는 미들웨어이다. 해외로 나갈 때 신원 확인을 위한 여권(passport)를 사용하는 것처럼 클라이언트가 서버에 요청을 보낼 수 있는지 심사하는 역할이라고 보면 된다.
strategy
passport는 어떤 것을 이용해 어떻게 인증을 구현할 것인가에 대한 전략인 strategy를 사용하는데 오늘을 기준으로 539가지 strategy가 있다고 공식 홈페이지에 나와 있다. 예를 들어 'passport-kakao'를 사용한다고 하면 카카오 계정을 통해 인증을 수행할 수 있는 것이다. 우리가 흔히 사용하는 소셜 로그인 외에도 OpenID, Heroku, HTTP Bearer 등 다양한 인증 방법이 있다. 서비스의 목적에 맞게 사용하면 되는데 보통의 웹 서비스는 주로 지역에서 구현하는 'passport-local'를 사용하게 된다. 이번에 나는 테스트를 위해 'passport-local'을 사용할 것이다. 이를 위해 $ npm install passport passport-local
커맨드를 실행하여 모듈을 설치하자.
app.js
const express = require('express');
const http = require('http');
const path = require('path');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const passport = require('passport');
const Localstrategy = require('passport-local').Strategy;
const app = express();
app.set('port', process.env.PORT || 3000);
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // POST 데이터 파싱
app.use(logger('dev'));
app.use(cookieParser('passport-test'));
app.use(session({
secret: 'passport-test',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false
}
}));
/* 가상 데이터 */
let fakeUser = {
username: 'test',
password: '1234'
};
/* passport 미들웨어 */
app.use(passport.initialize());
app.use(passport.session());
// 세션 처리 - 로그인에 성공했을 경우 딱 한번 호출되어 사용자의 식별자를 session에 저장
passport.serializeUser((user, done) => {
console.log('serializeUser', user);
done(null, user.username);
});
// 세션 처리 - 로그인 후 페이지 방문마다 사용자의 실제 데이터 주입
passport.deserializeUser((id, done) => {
console.log('deserializeUser', id);
done(null, fakeUser);
});
// passport-local Strategy 사용
passport.use(new Localstrategy((username, password, done) => {
if (username === fakeUser.username) {
if (password === fakeUser.password) {
return done(null, fakeUser);
} else {
return done(null, false, { message: "password incorrect" });
}
} else {
return done(null, false, { message: "username incorrect" });
}
}
));
const indexRouter = require('./routes/index');
app.use('/', indexRouter);
const userRouter = require('./routes/users');
app.use('/user', userRouter);
// login
app.post('/login', passport.authenticate('local', { failureRedirect: '/'}), (req, res) => {
res.redirect('/');
});
// logout
app.get("/logout", (req, res, next) => {
req.logout(() => {
res.redirect('/');
})
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.static(__dirname + '/public'));
/* 404 오류 처리 */
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 해당 주소가 없습니다.`)
error.status = 404;
next(error);
});
/* 오류 처리 미들웨어 */
app.use((err, req, res, next) => {
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'development' ? err : {};
res.status(err.status || 500);
res.send('error Occurred');
});
http.createServer(app).listen(app.get('port'), () => {
console.log('Express server listening on port ' + app.get('port'));
});
시간이 없어서 간단하게 설명하자면 fakeUser를 만들어서 해당 유저와의 정보를 비교하는 것이다. 코드에 간단하게 주석 처리를 해두었으니 해석이 어렵진 않을 것이다.
나는 로그인이 아예 안 되고 자꾸 아무런 오류도 출력되지 않고 redirection 돼서 한참을 헤맸는데 Post 데이터를 파싱해주는 app.use(express.urlencoded({ extended: true })); 가 없어서 그랬던 것 같다. 이 글을 읽는 중인데 해당 코드가 없다면 꼭 추가해주자..
그리고 로그아웃 기능도 안 됐는데 찾아보니 req.logout() 함수 안에 콜백 함수가 필요하다고 한다. 그래서 기존의 코드였던 아래 코드로 계속 해봤는데 안 됐던 것이다. 즉, 아래 코드는 잘못된 코드이다.
// logout
app.get("/logout", (req, res, next) => {
req.logout();
res.redirect('/');
});
240122 수정
더 찾아보니 다음 코드를 사용하면 콜백 함수를 쓰지 않아도 로그아웃 기능이 정상적으로 실행된다.
// logout
app.get("/logout", (req, res, next) => {
req.session.destroy();
res.redirect('/');
});
아무튼 프로그램을 실행하고 처음 localhost:3000에 접속하면 다음과 같이 뜬다.
여기에 fakeUser의 정보인 test와 1234를 비밀번호로 입력하면 다음과 같이 뜬다.
로그아웃 버튼을 누르면 처음에 봤던 초기화면으로 넘어가진다.
routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', (req, res) => {
if (!req.user) {
res.render('index', { title: 'Welcome page' });
} else {
const user = req.user.username;
res.render('user_page', { user: user });
}
});
module.exports = router;
views/index.jade
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
link(rel="stylesheet", href="/stylesheets/style.css")
title #{title}
body
form(action='/login', method='post')
div
label(for='username') Username:
input(type='text', name='username')
div
label(for='password') Password:
input(type='password', name='password')
div
input(type='submit', value='Log In')
views/user_page.jade
doctype html
html(lang='en')
head
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
link(rel="stylesheet", href="/stylesheets/style.css")
title User Page
body
p #{user}님 안녕하세요!
button(type='button', onclick="location.href='/logout'") Log Out
결론
이번엔 passport의 strategy 중 하나인 passport-local을 이용하여 간단한 로그인 테스트를 진행했다. 처음에 로그인이 왜 안 되는지 디버깅하는 과정에서 app.use(express.json()) 뿐만 아니라 app.use(express.urlencoded({ extended: true }))도 같이 사용해야 한다는 것을 알게 되면서 허무하지만 기쁜.. 뭐 그런 오묘한 감정이 들었다. 이렇게 하나하나 필수적인 코드를 추가해는 과정이 즐겁기도 하지만 나는 아직 멀었구나 라는 생각이 동시에 들기도 하는 그런 하루인 것 같다.
'StudyNote' 카테고리의 다른 글
도커(Docker) 가볍게 입문해보기 - 2 (3) | 2024.01.26 |
---|---|
Express.js와 nodemon (0) | 2024.01.23 |
Express.js의 morgan과 cookie-parser, express-session (2) | 2024.01.21 |
Express.js과 MVC 패턴, Controller & Service & Repository (3) | 2024.01.20 |
Express.js와 Prisma ORM을 사용한 CRUD API (0) | 2024.01.20 |