유저가 사이트에 회원가입을 할 수 있도록 기능을 구현했습니다.
이제 유저가 사이트에 로그인하고, 로그인한 회원의 정보를 이용하여 유저에게 다양한 기능을 제공할 수 있도록 해봅시다.
구현해야 할 사항은 다음과 같습니다.
로그인 기능은 /login
라우트에서 처리합니다.
GET 요청을 하면 로그인 페이지를 로딩하고,
POST 요청을 하면 사용자가 form 으로 보낸 유저 정보를 받아와 DB 에 입력된 사용자와 일치하는지 확인합니다.
import express from "express";
import {
getJoin,
postJoin,
getLogin,
postLogin,
} from "../controllers/userController";
const rootRouter = express.Router();
.
.
.
rootRouter.route("/login").get(getLogin).post(postLogin);
export default rootRouter;
<!-- login.html -->
<form method="POST">
<input placeholder="Username" type="text" name="username" required />
<input placeholder="Password" type="password" name="password" required />
<input type="submit" value="Login" />
</form>
<hr />
<div>
<span>
Don't have an account?
<a href="/join">Create one now →</a>
</span>
</div>
username 과 password 정보를 받아 POST 로 전송해주는 form 을 만들었습니다.
하단에는 링크를 걸어서, 미가입자일 경우 회원가입 페이지로 이동하게 합니다.
사용자가 /login
으로 요청을 보냈을 때 그에 대한 응답을 처리합니다.
로그인 페이지 뷰를 렌더링하는 코드를 작성합니다.
// userController.js 파일
export const getLogin = (req, res) =>
res.render("login.html", { pageTitle: "Login" });
login.html 페이지를 넘겨줍니다.
사용자가 /login
라우트로 GET 요청을 보내서 받은 login.html 페이지에서,
form 에 유저 정보를 입력하고 제출하면 /login
으로 POST 요청이 들어옵니다.
이 요청의 body
에 포함된 유저 정보를 이용하여 로그인 서비스를 구현합니다.
위와 같은 확인 절차가 필요합니다.
// userController.js 파일
import User from "models/User";
export const postLogin = async (req, res) => {
const { username, password } = req.body;
const pageTitle = "Login";
// username: username 인 유저가 있는지 확인
// findOne 으로 조건에 일치하는 doc 을 불러옴
const user = await User.findOne({ username });
if (!user) {
// 유저가 존재하지 않는다면 상태코드 변경 후 리턴
return res.status(400).render("login.html", {
pageTitle,
errorMessage: "An account with this username does not exists.",
});
}
/* bcrypt 를 이용하여 form 에 입력된 비밀번호와
DB 에 입력된 비밀번호가 일치하는지 확인
bcrypt.compare( ) 을 이용하여 자동으로 해싱, 솔트 적용 후 비교해줌
*/
const ok = await bcrypt.compare(password, user.password);
// 비밀번호가 일치하지 않으면 상태코드 변경 후 리턴
if (!ok) {
return res.status(400).render("login.html", {
pageTitle,
errorMessage: "Wrong password",
});
}
// 유저가 존재하고, 비밀번호도 일치한다면 루트 페이지로 리다이렉트
return res.redirect("/");
};
로그인 정보를 확인 후 오류가 있을 경우 다시 로그인 페이지로, 아무 오류도 없을 경우 메인 페이지로 돌아가도록 하였습니다.
유저가 로그인을 했으니 사이트는 유저 정보를 기억해야 합니다.
보통 웹 사이트에서 로그인을 하면 일정 시간동안은 다시 로그인을 안 해도 브라우저가 내 로그인 정보를 기억합니다.
하지만... 이건 원래 자연스러운 일이 아닙니다.
HTTP 프로토콜이 가지는 특성은 무상태성
과 비연결성
입니다.
브라우저는 서버에 요청
을 하고, 서버는 이에 응답
합니다.
한 번의 request-response
사이클이 끝나면 브라우저와 서버 간 연결도 끊어집니다. 연결을 계속 유지하지 않습니다.
이것이 비연결성
입니다.
또한, 다음 번 요청을 할 때 서버는 클라이언트의 상태를 기억하지 않습니다.
클라이언트가 로그인 정보를 보내고 서버가 로그인한 화면을 응답했다고 합시다.
다음 번 클라이언트의 요청에, 서버는
이 클라이언트는 아까 로그인한 애네? 이름이 홍길동 이었지...
하고 기억하지 않습니다.
이것이 무상태성
입니다.
즉, 로그인한 상태를 유지하고 싶다면 클라이언트는
매 요청에서 로그인 관련 정보를 보내야만 합니다.
매 화면마다 로그인을 해야 하는 건 불편합니다....
이를 해결하기 위해 쿠키
와 세션
을 사용합니다.
쿠키는 웹 브라우저가 가지는 클라이언트의 정보에 대한 문자열이고,
세션은 서버에서 관리되는 저장소라고 볼 수 있습니다.
세션은 쿠키를 이용하여 동작하는데, 쿠키에 포함된 세션 ID
정보로
세션에서 개별 클라이언트의 정보를 가져옵니다.
동작 과정은 다음과 같습니다.
이렇게 브라우저는 매 요청마다 쿠키를 전송하고,
서버는 쿠키의 세션 ID
정보로 해당 클라이언트를 식별하는 것이죠.
더 자세한 정보는 아래 링크를 참고합시다.
세션을 사용하기 위해 모듈을 설치해봅시다.
위 링크에 express-session 모듈과 관련한 설명이 나와있습니다.
터미널에서 npm i express-session
명령어를 입력하여 모듈을 설치합니다.
express-session 모듈을 다루기 위한 설명입니다.
- 클라이언트(브라우저)의 세션 관리
- 접속 시 세션 생성 후 세션 아이디를 담은 쿠키 전송
- 쿠키가 보유한 세션 아이디가 유효하지 않으면
새로운 세션 생성 후 쿠키를 다시 보냄
+ ex) 세션이 종료되어 세션 정보가 삭제되었을 때
- 생성된 세션은 Object
- 언제든지 세션에 요소를 추가하여 사용 가능
express app 을 생성한 js 파일에서 세션 미들웨어를 추가합니다.
import session from "express-session";
const app = express();
.
.
.
// 미들웨어로 세션 생성
app.use(
session({
secret: "Hello!",
resave: true,
saveUninitialized: true,
})
);
✅ session
미들웨어를 사용하면, store
에 JSON 형태로 세션 객체가 생성되고 req.session
객체로 세션에 접근할 수 있습니다.
express-session 문서를 읽어보면,
resave
와 saveUninitialized
속성은 병렬상황과 메모리 효율을 위해
false 로 설정하길 추천하고 있지만, 일단 기본 설정인 true 로 합니다.
속성값은 추후 용도에 맞게 변경하면 되겠습니다.
위처럼 설정하면 브라우저가 서버에 요청시, 서버에서 그를 위한 세션을 생성하고 세션 아이디 정보를 담은 쿠키를 보내줍니다.
세션 정보는 sessionStore
에 저장되며, 세션 종료시 초기화됩니다.
로그인을 하면 세션에 유저 정보와 로그인 여부를 저장하도록 설정합니다.
export const postLogin = async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
.
.
.
// 세션에 로그인 여부와 유저 정보 저장
// 세션에 loggedIn:true 와 user: {유저정보 object} 로 key:value 형태 정보 저장
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
};
req.session
으로 접속한 브라우저의 세션을 사용합니다.
세션에서 loggedIn
과 user
이라는 key 를 생성하고 value 를 넣어줍니다.
매 요청마다 세션에서 loggedIn
정보와 user
정보를 얻어올 수 있도록
미들웨어를 설정해봅시다.
import session from "express-session";
const app = express();
.
.
.
// 미들웨어로 세션 생성
app.use(
session({
secret: "Hello!",
resave: true,
saveUninitialized: true,
})
);
// 로그인 정보를 설정하는 미들웨어
// 따로 파일을 생성해서 설정해줘도 OK
app.user(function(req, res, next) {
// 세션에서 loggedIn 과 user 정보가 설정되었는지 확인
// res 의 locals 에 값을 가져와 설정
res.locals.loggedIn = Boolean(req.session.loggedIn);
res.locals.loggedInUser = req.session.user;
next();
}
)
req
와 res
는 하나의 request-response
사이클이 끝나면 없어지지만, 세션은 서버 메모리에 저장되며 request-response
사이클 하나가 끝나도 세션 종료 전까지 유지됩니다.
따라서, 로그인한 유저라면 req.session
에 해당 브라우저가 입력한
loggedIn
과 user
정보가 남아있습니다.
res.locals
를 사용하여 뷰 페이지에서 변수를 편하게 이용합니다.
res.locals ❓
request-response
사이클 동안 사용 가능- 뷰 페이지에서 변수를 이름만으로 간단히 사용 가능
res.locals
에서 선언한 변수는 별다른 과정 없이 이름만으로 뷰 페이지에서 사용할 수 있습니다.
{% if loggedIn %}
<li>
<!-- 이름만 사용하여 변수 사용! -->
<a href="/my-profile">{{loggedInUser.username}}'s Profile</a>
</li>
<li>
<a href="/users/logout">Log Out</a>
</li>
{% else %}
<li>
<a href="/join">Join</a>
</li>
<li>
<a href="/login">Log In</a>
</li>
{% endif %}
조건문을 사용하여 로그인했을 때와 아닐 때 보여줄 화면을 선택할 수 있습니다.
잘 보고 갑니다 !