본문 바로가기

clone toy projects/node_express_sns

Passport 모듈로 로그인 구현하기[로컬]

세션과 쿠키 처리 등 복잡한 작업이 많으므로 검증된 모듈을 사용하는 것이 좋습니다.

바로 Passport를 사용하는 것입니다.

이 모듈은 이름처럼 우리의 서비스를 사용할 수 있게 해주는 여권과 같은 역할을 합니다.

요즘에는 서비스에 로그인할 때 아이디와 비밀번호를 사용하지 않고 구글, 페이스북, 카카오톡 같은 기존의 SNS 서비스 계정으로 로그인하기도 합니다. 이 또한 Passport를 사용해서 해결할 수 있습니다.

 

 

$ npm i passport passport-local passport-kakao bcrypt

passport 관련 모듈들 설치

 

 

 

app.js에  passport연결

 

passport.initialize 미들웨어는 요청 (req 객체)에 Passport 설정을 심고

passport.session 미들웨어는 req.session 객체에 Passport 정보를 저장합니다. 

 

req.session 객체는 express-session에서 생성하는 것이므로 

Passport 미들웨어는 express-session 미들웨어보다 뒤에 연결해야 합니다. 

serializeUser로그인 시 실행되며, req.session (세션) 객체어떤 데이터를 저장할지를 정하는 메서드입니다. 

매개변수로 user를 받고 나서 done 함수에 두 번째 인수로 user.id를 넘기고 있습니다. 

done 함수 첫 번째 인수는 에러 발생 시 사용되고두 번째 인수에는 저장하고 싶은 데이터(사용자 아이디)를 넣습니다. 



deserializeUser는 매 요청 시 실행됩니다. 

passport.session 미들웨어가 이 메서드를 호출합니다. 

serializeUser의 done의 두 번째 인수로 넣었던 데이터가 deserializeUser의 매개변수가 됩니다. 

 

조금 전에 serializeUser에서 세션에 저장했던 아이디를 받아 데이터베이스에서 사용자 정보를 조회합니다. 

조회한 정보를 req.user에 저장하므로 앞으로 req.user를 통해 로그인한 사용자의 정보를 가져올 수 있습니다

 

즉, serializeUser는 사용자 정보 객체를 세션에 아이디로 저장하는 것이고, 

deserializeUser는 세션에 저장한 아이디를 통해 사용자 정보 객체를 불러오는 것입니다. 

세션에 불필요한 데이터를 담아두지 않기 위한 과정입니다.

전체 과정은 다음과 같습니다:

1. /auth/login 라우터를 통해 로그인 요청이 들어옴
2. 라우터에서 passport.authenticate 메서드 호출
3. 로그인 전략 수행
4. 로그인 성공 시 사용자 정보 객체와 함께 req.login 호출
5. req.login 메서드가 passport.serializeUser 호출
6. req.session에 사용자 아이디만 저장
7. 로그인 완료


1~4번은 아직 구현하지 않았습니다. 

로컬 로그인을 구현하면서 상응하는 코드를 볼 것입니다. 

 

로그인 이후의 과정입니다:

1. 요청이 들어옴
2. 라우터에 요청이 도달하기 전에 passport.session 미들웨어passport.deserializeUser 메서드 호출

3. connet.sid 세션쿠키 를 읽고 세션 객체를 찾아서 req.session으로 만듦

4. req.session에 저장된 아이디로 데이터베이스에서 사용자 조회
5. 조회된 사용자 정보를 req.user에 저장
6. 라우터에서 req.user 객체 사용 가능

 

 

 

본격적으로 로컬 로그인 구현하기

Passport에서 이를 구현하려면 passport-local 모듈이 필요합니다. 


먼저 회원가입, 로그인, 로그아웃 라우터를 만들어보겠습니다. 

이러한 라우터에는 접근 조건이 있습니다. 

이미 로그인한 사용자는 회원가입과 로그인 라우터에 접근하면 안되고, 이미 로그인한 상태이기 때문입니다. 

마찬가지로 로그인하지 않은 사용자는 로그아웃 라우터에 접근하면 안됩니다. 

 

따라서 라우터에 접근 권한을 제어하는 미들웨어가 필요합니다. 

미들웨어를 만들어보며 Passport가 req 객체에 추가해주는 req.isAuthenticated 메서드를 사용해봅시다.

Passport는 req 객체에 isAuthenticated 메서드를 추가합니다. 

로그인 중이면 req.isAuthenticated()가 true이고, 그렇지 않으면 false입니다. 

따라서 이 메서드를 사용하여 로그인 여부를 파악할 수 있습니다. 

isLoggedIn과 isNotLoggedIn 미들웨어를 만들었습니다.

자신의 프로필은 로그인을 해야 볼 수 있으므로 isLoggedIn 미들웨어를 사용합니다. 

req.isAuthenticated()가 true여야 next가 호출되어 res.render가 있는 미들웨어로 넘어갈 수 있습니다. 

false라면 로그인 창이 있는 메인 페이지로 리다이렉트됩니다.

회원가입 페이지는 로그인을 하지 않은 사람에게만 보여야 하므로 isNotLoggedIn 미들웨어로 

req.isAuthenticated()가 false일 때만 next를 호출하도록 했습니다.


res.locals.user 속성에 req.user를 넣은 것을 주목해주세요. 

이를 통해 사용자 정보에 접근할 수 있게 되었습니다.

이제 회원가입, 로그인, 로그아웃 라우터를 작성해봅시다.

auth 라우터

 

auth 컨트롤러

 

 

나중에 app.js와 연결할 때 /auth 접두사를 붙일 것이므로 라우터의 주소는 각각 /auth/join, /auth/login, /auth/logout가 됩니다.

회원가입 컨트롤러

 

 

로그인 컨트롤러

로그인 요청이 들어오면 passport.authenticate('local') 미들웨어가 로컬 로그인 전략을 수행합니다. 

이 미들웨어는 라우터 미들웨어 안에 들어있습니다.

만약 미들웨어에 사용자 정의 기능을 추가하고 싶을 때에는 내부 미들웨어에 (req, res, next)를 인수로 제공해서 호출하면 됩니다. 

 

전략 코드는 잠시 후에 작성합니다. 전략이 성공하거나 실패하면 authenticate 메서드의 콜백 함수가 실행됩니다. 

콜백 함수의 첫 번째 매개변수 authErr 값이 있다면 실패한 것이고, 

두 번째 매개변수 값이 있다면 성공한 것이고, req.login 메서드를 호출합니다. 

 

Passport는 req 객체에 login과 logout 메서드를 추가합니다. 

req.login은 passport.serializeUser를 호출합니다. 

req.login에 제공하는 user 객체가 serializeUser로 넘어가게 됩니다.

또하  이때 connect.sid의 세션 쿠키가 브라우저에 전송됩니다.


로그아웃 컨트롤러

req.logout 메서드는 req.user req.session 객체를 제거합니다.

req.logout 메서드는 콜백함수를 인수로 받고, 세션 정보를 지운 후 콜백 함수가 실행됩니다.

콜백 함수에서는 메인 페이지로 되돌아가면 됩니다. 로그인이 헤제되어 있을 것입니다.

 

 

이제 로그인 전략을 구현했으니 passport-local 모듈에서 Strategy 생성자를 불러와 그안에 전략을 구현하면 됩니다.


LocalStrategy 생성자의 첫 번째 인수로 주어진 객체는 전략에 관한 설정을 하는 곳입니다.

usernameField와 passwordField에는 일치하는 로그인 라우터의 req.body 속성명을 적으면 됩니다.

req.body.email에 이메일 주소가, req.body.password에 비밀번호가 담겨들어오므로 email과 password를 각각 넣었습니다.

실제 전략을 수행하는 async 함수입니다. LocalStrategy 생성자의 두 번째 인수로 들어갑니다. 

첫 번째 인수에서 넣어준 email과 password는 각각 async 함수의 첫 번째와 두 번째 매개변수가 됩니다. 

세 번째 매개변수인 done 함수는 passport.authenticate의 콜백 함수입니다.

전략의 내용은 다음과 같습니다:

먼저 사용자 데이터베이스에서 일치하는 이메일이 있는지 찾은 후, 있다면 bcrypt의 compare 함수로 비밀번호를 비교합니다.
비밀번호까지 일치한다면 done 함수의 두 번째 인수로 사용자 정보를 넣어 보냅니다. 

 

done 함수가 호출된 후에는 다시 passport.authenticate의 콜백 함수에서 나머지 로직이 실행됩니다. 

로그인에 성공했다면 메인 페이지로 리다이렉트되면서 로그인 폼 대신 회원정보가 뜰 것입니다. 

 

현재까지는 auth 라우터를 연결하지 않았으므로 코드가 동작하지 않습니다. 

카카오 로그인까지 구현한 후에 연결해보세요.