세션을 이용해서 로그인을 구현할 때 많이 사용하는 것이 passport.js 모듈이다. 오늘은 이 passport 를 이용한 로그인에관하여 글을 써보려 한다.
먼저 이번에는 소셜 로그인이 아니라 일반적인 로그인 방식을 사용한다. 따라서 passport , passport-local 모듈을 사용했다.
세션 정보 저장은 SQLiteStore 을 사용하였고 이를 VScode 에서 볼 수 있게 SQLite Viewer 익스텐션을 설치하였다
이후 필요한 미들웨어들은 글을 작성하면서 쓸 예정
공식문서에 나와있는 Passport 에 대한 간략한 설명이다. Passport 는 request 에 대한 진위성을 증명하기 위해 사용되는 middleware 라고 작성되어있다. 다르게 말하면 이 passport는 request 와 response 사이에 위치하면서 해당 사용자를 식별하게 해주는 역할을 하면 된다고 이해하면 될 듯 하다.
먼저 passport.authenticate() 를 공식문서에 코드를 따라가면서 이해해보자. passport.authenticate() 는 middleware 로써, 사용자 인증이 성공한 경우, req.user 속성이 해당 사용자에게 부여되고, 세션이 생성된다. 그리고 그 이후에 나오는 콜백함수가 실행된다.
코드의 구조와 함께 이해를 해 보면, 먼저 passport.authenticate 의 첫번째 인자는 현재 사용할 strategy 를 나타낸다. 지금은 local 을 사용하므로 local 로 작성했다. 그리고 두번째는 인증 이후의 option 이 들어간다. 만약 인증이 실패한 경우, '/login' 페이지로 다시 redirect 되고 콜백함수는 실행되지 않는다. 이에 반해 만약 인증이 성공하면 콜백이 실행된다.
그러면 이제 authenticate 함수에서 사용될 strategy 를 정의하는 부분을 살펴보자.
현재 local 을 사용하므로, passport-local 을 설치하고 new LocalStrategy 로 정의를 해줬다. 이 localStrategy 는 살짝 다르게 사용해서 내가 작성한 코드로 기록을 남겨두려한다. LocalStrategy 의 두번째 인자로 들어가는 함수를 먼저 보자. 이 함수는 request 로 들어오는 정보 (credential) 을 파싱하고 에러가 나는지 , 옳바른 정보인지를 확인한다. 아래 사진의 내 함수 부분은 위의 공식문서의 verify 함수와 비슷한 역할을 한다. 공식문서 코드로 보면, 만약 err 인 경우, err 를 리턴, request 에서 들어온 credential 과 DB 의 정보를 비교했을 때, 해당 유저의 정보와 다를 경우 Incorrect .... 그리고 옳바른 정보일 경우 cb(null , user) 를 리턴한다. 이 콜백함수의 형태는 다음과 같다.
이 cb 함수는 위에서 설명했던 authenticate 의 콜백함수로 들어간다.
login 이 성공한 경우 , authError 는 Null, user 에는 user 정보를 전달
login 이 실패한 경우, authError 는 null , user 에는 로그인이 실패했으므로 false 가 들어간다.
서버 에러시 authError 에 error 메시지가 들어간다. 아래 나의 코드에서도 비슷한 방식으로 작성함.
그러면 이 localStrategy 의 첫번째 인자로 들어가는 값은 뭘까? 이 부분은 LocalStrategy 에 option 을 부여한다. 맨 처음 2개의 옵션은 요청이 들어온 값 들 중에서 "username" 이라는 키로 넘어온 값을 id , "password" 라는 키으로 넘어온 값을 pw 로 전달한다는 의미이다. 그리고 session 을 사용하고 , callback 함수에 request 를 전달할 지 여부도 옵션으로 나타낼 수 있다.
다시 원래 두번째 인자로 돌아가보자. done 함수가 실행되면 다시 authenticate 로 돌아간다. 이때 done 함수와 authenticate 의 콜백함수를 중점적으로 보자.
done 함수의 각 인자들은 그대로 authenticate 의 콜백함수의 인자로 들어간다. 만약 로그인이 성공한 경우, err 에는 null , user 에는 user 정보가 들어간다. 이에 반해 로그인이 실패한 경우, err 에 null , user에는 false 가 들어간다.
serializeUser : user 객체를 req.session 에 저장하는 역할, 현재 user.id 만 저장한다
deserializeUser : session 에 저장된 값을 이용해서 사용자 정보를 db 에서 찾고, http request 에 리턴한다. (req.user 값으로 등록된다). 조금 더 자세히 설명하면 ,세션에 저장되는 정보가 클 경우, 메모리가 많이 소모되므로, serializeUser 에서 user.id 값만 저장하고 이후 이 id를 이용해 db에 접근하여 select 한 후 HTTP request 에 붙여서 (req.user 에 등록해) 반환한다.
만약 로그인이 성공한 경우, req.login 함수가 실행된다. 이때 user 는 콜백함수에서 전달받은 user 를 그대로 가져간다. req.login 함수는 이후에 바로 serializeUser() 함수를 호출한다. 이 serializeUser 함수가 session 에 user 의 정보를 등록한다.
그리고 바로 deserialize 함수가 실행되는데, serialize 함수에서 req.session 에 등록했던 user.id 를 이용해서 db에 접근해 select 후 http request 의 req.user 객에 붙여서 반환한다.
그리고 res.redirect('/') 로 응답하면, 세션쿠기를 브라우저에 보낸다.
지금까지의 과정이 로그인 처리
전체 흐름
Passport 로그인 이후, 모든 요청에 대해 passport.session() 미들웨어가 passport.deserialize 함수를 호출한다. deserializeUser 에서 req.session 에 등록된 아이디로 db 조회후 req.user 에 사용자 전체 정보를 등록해 전달
동작 방식