최근에 네이티브 앱에서 세션을 기반으로한 인증을 구현할 필요가 있었는데 브라우저와 달리 메뉴얼로 세션을 핸들링해야 했다. 서버에서 받은 쿠키를 저장하고 다시 이를 서버로 보내주는 로직이었는데 하다보니 좀 더 구체적인 모습으로 세션이 어떻게 핸들링 되는지 알 필요가 있었다. 그래서 경우의 수 별로 각각 flow를 찾아 정리해 보았다. 기존 많은 설명들이 serealizeUser와 deserializeUser이 간략하게 무엇을 하는지만 설명했었는데. 대체 이들 함수 실행 전후로 무슨 일이 일어나는지 알고 싶었다. 이 글은 각 상황에서 아래 3개의 미들웨어가 어떻게 상호작용하는지에 대한 설명이다.(틀린 부분이 있다면 지적 부탁드립니다.)
express-session : 세션 복원 및 쿠키 발급/조회
sid => req.session
passport.initialize : 인증유저 초기화
req.session.passport.user => user.id
passport.session : 유저객체 복원
user.id => req.user
유저가 id, pwd를 입력해서 /login 경로로 post 요청을 할 때, 요청 헤더에는 기존에 받았던 sid가 삽입되어 전달된다.
express-session 미들웨어는 요청 헤더의 쿠키를 매번 확인하는데 이번에는 sid를 확인할 수 있다.
이를 세션저장소(mongostore or memoryStore)에서 조회한 뒤 해당 sid(해시값)와 일치하는 세션id(실제값)를 찾아 해당 세션객체를 req.session에 세팅한다.
이후 passport.authenticate() 의 로직을 통과한 인증된 user 객체가 req.login() => serealizeUser에 의해 req.session에다가 passport : { user : user.id } } 를 추가한다.(아래 사진 참고)
마지막으로, 전체 유저 오브젝트를 req.user에 붙인다.
이후 서버 상에서 로그인한 유저의 user 객체를 프론트로 넘겨준다.
자 이때, 우리가 위에서 붙였던 req.session.passport.user = user.id 는 세션 저장소에 저장된다. req.session.save() => 자동 처리된다.
서버의 응답에는 sid가 여전히 전달된다.
*세션 저장소에 저장된 세션 객체의 모습은 아래와 같다.(json)
쿠키가 만료되기 전까지 모든 요청에는 sid가 지속적으로 전달된다.
express-session 미들웨어는 요청 헤더의 sid 쿠키를 확인하고 세션 저장소에서 꺼내 세션 객체를 복원시킨다.
이때, req.session의 모습은 이전에 세션저장소에 저장했던 객체의 모습 그대로다.
만약 쿠키가 만료됐다면, 해당 sid는 기존의 세션 객체를 복원하지 못할 것이다.
passport.initialize() 미들웨어는 req.session에서 req.session.passport.user 가 있는지 확인한다.
그리고 아래 passport.session()미들웨어에 user.id를 넘긴다.
저장된 세션 객체 내에 passport.user는, 해당 sid에 대응하는 인증된 유저를 의미한다.
즉, 인증된 유저가 없다면 세션이 만료 되었거나 뭔가 문제가 있는 것. 다시 인증을 거쳐야 한다.
passport.session()의 실행과정에서 아래의 deserializeUser가 실행된다.
이 내부에서 해당 user.id를 deserializeUser 의 2번째 인자로 넘겨준다.
deserializeUser는 db에서 user.id를 조회하여 user 객체 전체를 복원한다.
이후 done(null, user)를 하면, req.user가 생긴다.
passport-local
에 대해 알아야 하는 모든 것 translated by jake seohttps://stackoverflow.com/questions/27637609/understanding-passport-serialize-deserialize
미들웨어의 순서가 포인트. 인증 요청이 언제 시작되고 어떻게 진행되는지 아는게 중요합니다.
passport.session은 req._passport.session 안에 있는 user필드를 찾는다. 그리고 만약 찾았다면, deserializeUser 함수를 실행하면서 deserializeUser 함수에 user를 인자로 전달한다. deserializeUser 함수는 req._passport.session.user를 request 객체의 user 필드에 할당한다.(req._passport.session.user가 있는 경우에만)
만약 위와같이 serializeUser에서 user를 stringify를 했다면, deserializeUser 에서는 parse해주어야 한다.
deserializeUser 함수는 패스포트를 설치할 때 너의 콜백을 _deserializeUsers 함수 스택에 놓기 위해 처음 호출된다. 두번째로 실행되는 건, passport.session 미들웨어에서 request 객체에 user 필드를 할당하기 위해 호출된다. 우리가 deserializeUser 안에 정의한 콜백은 request 객체에 user필드가 할당되기 전에 실행된다.
serializeUser 역시 패스포트가 설치될 때 처음으로 호출된다. 하지만 이는 user 객체를 직렬화 해서 세션에 저장하기 위해서 이용될 뿐이다. 두번째로 실행되는 건, Passport에 의해 할당된 login / logIn (별칭) 메서드(역자 : req.login()을 말하는 듯)에서 호출되어 세션 내에 사용자 객체를 저장하는 데 사용됩니다. serializeUser 함수는 또한 _serializers 스택 안에 이미 내장된 함수( 우리가 패스포트를 설치할 때 할당 되어진 것)가 있는지도 확인하고 실행합니다.
그러면 user 객체가 할당되거나, user.id가 req._passport.session.user에 할당됩니다. session 필드가 직접적으로 req.session.passport 필드를 참조한다는 잊으면 안됩니다. 그럼으로써, user가 세션 내에 저장됩니다.(왜냐하면 req._passport.session은 req.session.passport를 참조하고, req._passport.session은 매 요청마다 passport.initialize 미들웨어에 의해 수정되기 때문입니다. 요청이 끝나면, req.session 데이터는 세션 스토어에 저장될 것입니다.
성공적으로 인증을 마친 뒤, 그 다음 요청이 시작될 때 어떤 일이 일어날까요?
세션 미들웨어는 이미 유저 데이터가 저장된 세션 스토어로부터 세션을 얻는다.
passport.initialize는 어디에? 세션이 있는지 확인하고, req.session.passport를 req_passport.session에 할당한다.
passport.session은 req._passport.session.user 를 확인하고 deserialize 한다. 이 단계에서, 우리는 req.user를 얻고, req.isAuthenticated는 true를 리턴한다.(req._passport.session.user가 있다면)