전략이라는 단어를 사용해서 여러 로그인 전략을 사용할 수 있도록 도와주는 패키지다.
id
, password
)passport.authenticate()
호출passport.use()
로 등록해놓은 전략을 실행한다.done()
으로 passport.authenticate()
의 두번째 인자인 콜백함수를 실행하고, done()
에서 넣어준 3개의 인자를 넘겨준다.req.login()
을 호출해준다.req.login()
에서 클라이언트로 세션쿠키를 만들어서 넘겨준다. ( 아마 내부적으로 실행됨 )passport.serializeUser()
가 실행된다.done()
에 넣어주고 그 값이 세션에 저장시켜진다.passport.deserializeUser()
이 자동적으로 호출되며,done()
에 전달한 현재 로그인한 유저의 데이터가 req.user
에 들어가게 된다.passport.deserializeUser()
의 인자로 넘겨주는 것 같다.디테일한 부분은 생략하고 가장 기본적인 구조만 작성했음
실행의 흐름을 보기 편하도록 하나의 파일에 모두 작성했음
const express = require("express");
const session = require("express-session");
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const app = express();
// passport내부적으로 세션을 이용하기 때문에 express-session을 등록함
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
}));
// 밑에 두개는 정확히 뭔지는 모르겠지만 등록해줘야 오류없이 사용가능함
app.use(passport.initialize());
app.use(passport.session());
/**
* localStrategy()의 콜백의 3번째 인자인 done()의 두번째 인자의 값이
* passport.serializeUser()의 콜백의 첫번째 인자로 들어온다.
* req.login() 실행시 여기가 실행된다.
* 즉, 로그인 성공시 한번만 실행되는 부분이다.
* 여기서는 세션에 저장할 데이터를 넘겨준다.
* 식별자 하나만 저장해두는 이유는 세션은 서버측의 메모리를 사용하기 때문에
* 다수의 유저가 로그인할수록 서버에 부담이 커지기 때문에 최소한의 데이터만 유지하는 것임
*/
passport.serializeUser((user, done) => {
done(null, user.id);
});
/**
* 여기는 로그인 성공 이후의 호출부터 로그아웃전까지 계속 실행하는 부분이다.
* passport.serializeUser()의 done()에서 전달한 값(식별자)이 첫번째 인자로 들어오고
* 그 값을 이용해서 유저 전체정보를 찾아서 done()에 전달해준다.
* 그렇게 되면 req.user에 해당 값이 들어가게 된다. 즉, req.user에 현재 로그인한 유저의 전체 정보가 들어가게 된다.
**/
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findOne({ where: { id } });
done(null, user);
} catch (error) {
console.error("deserializeUser >> ", error);
done(error);
}
});
/**
* 로컬 전략을 등록하는 부분이다.
* passport.authenticate("local", callback)을 호출하면 "local"이라는 문자열을 보고
* 등록된 local전략을 실행하게 된다.
* 첫번째 인자는 req.body로 받은 로그인할 유저의 데이터의 이름을 변경하는 부분이다
* 기본적으로 req.body.username, req.body.password 이지만 그 값을 변경하는 부분이다.
* 아래는 req.body.email로 받으라고 passport에게 알려주는 것임
* 두번째 인자인 콜백에 인수로 req.body의 값들이 들어온다.
* done()은 done(서버측에러, 성공한유저정보, 클라이언트측에러) 순으로 값을 넣어야 한다.
* 아래는 아이디와 비밀번호가 일치하는 유저가 존재하는지 확인후에 존재하면 해당 유저의 정보를 반환하는 내용임
*/
passport.use(
new localStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
const user = await User.findOne({ where: { email } });
if (!user) {
return done(null, false, { reason: "존재하지 않는 이메일입니다." });
}
const result = await bcrypt.compare(password, user.password);
if (result) {
return done(null, user);
}
return done(null, false, { reason: "비밀번호가 틀렸습니다." });
} catch (error) {
console.error("passport local >> ", error);
return done(error);
}
}
)
);
app.post("/login", (req, res, next) => {
// 여기서 "local"만 확인하고 local전략에 가서 실행한 후 done()에 넣어준 인자들이
// passport.authenticate()의 두번째 인자인 콜백함수의 인자로 들어온다.
passport.authenticate("local", (error, user, info) => {
// 서버측 에러일 경우
if (error) {
console.error("POST /api/user/login1 >> ", error);
return next(error);
}
// 클라이언트측 에러일 경우 ( 아이디 혹은 패스워드 불일치 )
if (info) {
return res.status(401).send({ result: false, message: info.reason });
}
/**
* 로그인 성공 시 실행
* 여기서 passport가 사용할 세션 쿠키를 만들어서 브라우저에게 전송해 준다.
* 그것 외에도 클라이언트 측에 보내줄 응답 데이터를 전송한다.
*/
return req.login(user, async (loginError) => {
if (loginError) {
console.error("POST /api/user/login2 >> ", error);
return next(loginError);
}
try {
const userWithoutPassword = await User.findOne({
where: {
id: {
[Op.eq]: user.id,
},
},
attributes: ["id", "nickname", "email"],
include: [
{
model: Post,
},
{
model: User,
as: "Followings",
},
{
model: User,
as: "Followers",
},
],
});
return res.json(userWithoutPassword);
} catch (error) {}
});
})(req, res, next);
});
cors
문제 해결해야 제대로 테스트할 수 있음axios
를 사용시 withCredentials: true
옵션을 명시해줘야 하며, 서버측에서도 cors
에서 credentials: true
속성을 줘야한다.실행의 흐름은 어느 정도 이해한 것 같은데 공식 문서를 읽은 것도 아니고 그냥 여기저기서 주워들은 지식으로 정리한 거라 정확하지 않은 정보들이 많을 수 있어서 추후에 공식 문서 읽은 후 다시 정리할 생각이다.