
passport는 Node.js를 위한 인증 미들웨어 이다. 전략이라는 명칭으로 다양한 Federated Login을 도와준다.
passport는 사용하기엔 편할 수 있지만 사용하기 이전에 몇가지 개념에 대해서 미리 숙지 하고 있지 않으면 사용하면서도 이해하기 어렵다고 생각한다. 개인적으로 본인은 이해가 어려웠음. 따라서 아래 관련 개념을 간략히 정리 해보자면:
쿠키는 간단히 이야기하면 브라우저가 가지고 있는 작은 저장소라고 생각하면 된다.
쿠키는 서버쪽에서 응답에 Set-Cookie라는 키값으로 헤더에 실어서 응답에 같이 보내는 정보이다.
서버로 온 요청에 쿠키를 등록해 응답에 되돌려보낸 후 이후 요청부터는 브라우저로부터 매 요청시마다 쿠키를 같이 보내준다. 이 쿠키로 인해 서버는 이 요청이 어떤 곳에서부터 온 요청인지 파악할 수 있게 된다. (이말은 HTTP의 stateless, connectionless의 성질을 알면 이해가 된다.)
❗따라서 쿠키는 사용자의 치명적인 정보가 절대로 담는 용도로 사용되어선 안된다.
❗쿠키자체가 설사 아무 정보를 담고 있지 않다 할지라도 쿠키자체만으로도 사용자의 동의없이 탈취되어선 안된다.
❗따라서 운영환경에서는 쿠키의 여러 옵션(httpOnly, secure 등)을 통해 보안에 유의해야한다.
ℹ️ 만약 프론트와 백엔드의 서버가 서로 다를 경우 쿠키를 전달하기 위해서는 각 프론트 백엔드 서버 모두 credentials 값이 true 되어야한다.
세션은 서버측에서 저장하고 있는 데이터이다. 보통 아주 아주 아주 기본적인 사용자의 대한 정보를 담는다. 이메일이나 사용자 id가 아닌 사용자를 식별할 수 있는 유니크한 값.
쿠키의 세션ID ➡️ 세션 값 ➡️ DB의 사용자 정보
위와 같은 방식으로 서버는 매 요청으로부터 사용자를 식별할 수 있다.
OAuth는 페이스북, 트위터, 네이버, 카카오톡 등과 같은 (provider)가 제공하는 API를 사용할 수 있는 토큰발행을 받는 목적으로 한다.
이런 토큰들은 provider가 제공하는 resource를 사용할 수 있는 접근 권한도 포함되어 있다. 따라서 이런 토큰을 발행받기 위해서는 실제 소유자인 resource owner 에게 승인을 받는것이 필수이다. 승인을 받게 되면 provider는 client(내 서버)를 해당 권한을 사용할 수 있게 토큰을 발행해준다.
토큰을 발행받은 client는 위임된 권한 내에서 provider가 제공하는 resource에 접근이 가능하다.
ℹ️ OAuth2.0은 OAuth1.0과 달리 토큰의 life-time을 지정할 수 있도록 함.
passport를 이해하는데 위에 개념이 어느정도 있다면 매우 쉽게 사용할 수 있을 것이라고 생각함.
passport는 크게 초기화, 전략구현, 라우트 파트로 나누어 구현하면 된다.
✅ passport는 세션을 사용하기 때문에 세션의 미들웨어 아래에 초기화를 해줘야한다.
app.use(
session(
process.env.NODE_ENV === 'production'
? sesseionProdOption
: sesseionDevOption
)
)
app.use(passport.initialize())
app.use(passport.session())
Top-level 폴더에 passport를 만들고 그 아래 전략들을 구현하여 모듈화하여 구현하면 관리하기 편하다.
✅ serializeUser, deserializeUser는 passport폴더내 index.js에서 구현하고 이것을 app에 호출해준다.
// passport/index.js
module.exports = () => {
passport.serializeUser((user, done) => {
return done(null, user.id)
})
passport.deserializeUser(async (id, done) => {
try {
console.log('🪀🪀🪀 deserializeUser')
const user = await User.findOne({
where: { id }
})
return done(null, user)
} catch (error) {
if (error) {
logger.error(error)
return done(error, false)
}
}
})
local()
github()
google()
kakao()
facebook()
}
✅ 전략들은 아래그림과 같은 구조로 전략들을 각각 구현하고 그것들을 index에서 호출한다.

라우트 파트는 로그인버튼 클릭시 호출되는 라우트와 provider로부터 받는 callback을 위한 라우트로 각 전략별로 2개씩이 필요하다.
✅ 로컬로그인으로 흐름을 정리하자면 아래와 같다.
auth/login이라고 할 때 이 라우트의 미들웨어로 passport를 사용하고 이 passport의 전략으로 흐름 진행됨해당 사용자가 정상적으로 있는경우 로컬전략의 로직 구현부에서 done(null, user)로 콜백이 발생한다.local전략의 경우에는 req.login() 함수를 호출해야 session에 등록됨(serializeUser 호출됨😀😀)serializeUser(user, done)에 user를 콜백하도록 약속되어있음. 이때 이 user의 식별자값을 호출 done(null, user.id) (여기선 id인데 어떤식별자값이던 상관 없음.)user.id로 할 경우
세션에id값이passport: {user: 아이디값}의 형태로 등록됨.
serializeUser는 즉 로그인에 성공한 user의 식별자를 세션에 등록하는 역할을 하는 것이다.
따라서 로그인이 성공하면 딱 한번호출되는 것이 있다면 이 세션에 등록하는 과정인 serializeUser이다.
deserializeUser이 호출된다.deserializeUser는 app.use(passport.session()) 이 미들웨어가 호출하는 것이므로 이것에 아래 있는 라우팅경로에 호출들은 무조건 다 호출된다.
이렇게 호출된 deserializeUser는 세션에 등록된 id값을 통해 user를 DB에서 조회할 수 있다. user가 조회되면 done(null, user)의 콜백을 통해 전달해주고 전달된 user는 req.user 에 등록된다. req.user는 passport에서만 사용가능하다.
따라서 passport를 사용하지 않으면 req.user라는 객체는 없다. (따로 만들어 넣지 않는 이상)