Juni_Dev_log

(node.js) [Part.9] 패스포트로 사용자 인증하기 - 패스포트로 로그인하기 본문

Theorem (정리)/node.js

(node.js) [Part.9] 패스포트로 사용자 인증하기 - 패스포트로 로그인하기

Juni_K 2021. 2. 1. 12:26

passport.js

패스포트(Passport)는 노드에서 사용할 수 있는 사용자 인증 모듈이다.

이 모듈은 사용 방법이 간단할 뿐만 아니라, 사용자 인증 기능을 독립된 모듈 안에서 진행할 수 있도록 도와준다.

특히 익스프레스를 사용할 경우에는 미들웨어로 끼워 넣을 수 있어 몇 가지 간단한 설정만으로도 로그인 기능을 구현할 수 있다.

 

패스포트 모듈의 유일한 목적은 클라이언트에서 요청한 인증 정보(아이디나 비밀번호)로 사용자 인증을 하는 것이다.

 

따라서 이외의 기능은 패스포트 모듈이 아닌, 다른 코드에서 담당해야한다. 순전히 인증 기능만을 담당하는 모듈이다.

 

패스포트는 수백 가지의 인증방식을 제공하는데, 어떤 인증 방식을 사용할 것인지 결정하는 것이 스트래티지(Strategy)이다. 

 

각각의 인증 방식은 각각의 스트래지로 만들어져있기 때문에 어떤 스트래지를 사용하느냐에 따라서 인증 방식이 달라진다. 대표적인 인증 방식으로는 데이터베이스에 저장된 사용자 정보와 비교하는 로컬 인증 방식(Local Strategty), 페이스북이나 트위터의 계정을 사용하는 OAuth 인증 방식 등이 있다.

 

다음은 패스포트 미들웨어를 사용해 웹 서버에서 사용자를 인증하는 방식을 보여준다.

 

패스포트를 사용해 웹 서버에서 사용자를 인증하는 구조

 

- 웹 브라우저에서 사용자 인증을 요청할 때는 단순히 웹 서버의 데이터베이스에 저장된 아이디와 비밀번호를 비교하도록 만들 수 있다.

- 또한, 페이스북이나 구글의 계정을 사용해서 인증하도록 만들 수도 있다.

- 클라이언트가 인증을 요청하면 웹 서버에 있는 패스포트 모듈은 미리 설정해 둔 인증 방식으로 사용자를 인증한 후, 성공하면 사용자 정보를 세션에 저장한다.

- 이 세션 정보는 정상적으로 사용자 인증이 되었을 때만 사용할 수 있으므로 로그인 이후의 요청 정보를 처리할 때는 세션 정보를 확인함으로써 사용자가 로그인되었는지 아닌지를 구별할 수 있다.

 

📌 패스포트의 기본 사용 방법 살펴보기

인증은 보통 복잡한 과정을 거치지만, 패스포트를 사용하면 그리 복잡하지 않다.

인증 절차를 위해 우선 알아둘 것은 다음과 같은 코드를 자주 사용한다는 것이다.

1
2
3
4
5
6
router.route('/login').post(passport.authenticate('local',
    {
        successRedirect : '/',
        failureRedirect : '/login'
    }
});
cs

이 코드 형태는 바로 라우터 객체에 등록하는 라우팅 함수이다.

- 클라이언트에서 POST 방식으로 요청하는 요청 패스가 /login일 때 호출된 함수를 설정하고 있다.

- 라우팅 함수 부분은 passport.authenticate() 함수를 호출하는 것으로 설정되어 있다.

 

passport 객체는 패스포트 모듈을 불러들이는 객체이다.

그렇다면 passport 모듈을 npm에서 설치해보자.

 

⚙️ 설치

%npm install passport --save

 

클라이언트에서 보낸 인증정보를 처리하려면 passport.authenticate() 메소드를 호출하면서 동시에 어떤 스트래티지(Strategy)를 사용할지 지정해야한다.

 

예를 들어,

1
2
3
4
5
6
7
router.route('/login').post(passport.authenticate('local'),
    function(req,res){
    //인증에 성공했을 때 호출됨
    //'req.user'는 인증된 사용자의 정보임
    res.redirect('/users/' + req.user.username);
    }
);
cs

- passport.authenticate() 메소드를 호출하면서 'local' 인증방식을 결정하는 스트래티지를 전달한다. (이름은 해당 스트래티지를 설정할 때 지정한다.)

 

예를들어, 데이터베이스에 저장된 사용자 아이디와 비밀번호로 인증하는 로컬 인증을 사용한다면, 로컬 스트래티지(Local Strategy)를 설정할 때 이름을 부여하고 그 이름은 authenticate() 메소드를 호출할 때 사용한다.

이때 스트래티지는 라우팅 함수에서 사용되기 전에 먼저 실행해야한다. authenticate() 메소드를 호출하여 인증을 시도한 후, 인증에 실패했을 때는 디폴트 값으로 401 Unauthorized 상태가 응답으로 돌아온다.

그리고 다른 라우팅 함수들은 더 이상 호출되지 않는다. 

 

만약, 인증이 성공했다면 라우팅 함수가 호출되며 req.user 속성에 인증된 사용자 정보가 들어간다.

 

authenticate() 호출 결과에 따른 처리

 

앞에서 살펴본 코드에서 인증에 성공했을 때 콜백함수가 호출되도록 설정했다.

그리고 그 안에서 응답 객체의 redirect() 메소드를 호출하면서 동시에 다른 요청 패스로 이동하도록 했다.

 

이렇게 리다이렉트를 사용해 다른 요청 패스로 이동하는 경우는 상당히 많다. 따라서 인증에 성공했을 때와 실패했을 때 어떻게 리다이렉트할 것인지에 대한 정보를 파라미터로 전달하는 코드를 자주 사용한다.

 

일반적인 코드 형태는 다음과 같다.

1
2
3
4
5
6
router.route('/login').post(passport.authenticate('local',
    {
        successRedirect : '/',
        failureRedirect : '/login'
    }
});
cs

- 인증에 성공했을 때, 사용자가 홈으로 리다이렉트된다.

- 인증에 실패했을 때, 사용자는 로그인 페이지로 리다이렉트된다.

 

📌 플래시 메시지와 커스텀 콜백 이해하기

리다이랙트를 사용해 응답을 보낼 때는 보통 플래시 메시지(Flash Message)를 같이 사용한다.

 

플래시 메시지는, 상태 메시지를 응답 웹 문서 쪽으로 전달할 때 사용한다. 플래시 메시지를 사용하려면 우선 connect-flash 외장 모듈을 설치해야한다.

 

⚙️설치

%npm install connect-flash --save

 

flash() 메소드를 사용한 플래시 메시지의 설정과 조회

플래시 메시지를 사용하는 방법은 아주 간단하다.

요청 객체의 flash() 메소드를 사용할 때 파라미터가 두 개면 플래시 메시지를 설정하는 것이고, 파라미터가 하나면 플래시 메시지를 조회하는 것이다.

 

passport.authenticate() 메소드를 호출할 때, failureFlash 옵션을 줄 수 있다. 그러면 패스포트로 인증하는 과정에서 오류가 발생했을 때, 플래시 메시지로 오류가 전달된다.

즉, 코드에서 명시적으로 req.flash() 메소드를 호출하는 것이 아니라, 패스포트 모듈이 자동으로 flash() 메소드를 호출하면서 오류 메시지를 설정한다.

 

이렇게 작성하면 플래시 메시지를 꺼내서 확인할 수 있다.

1
2
3
4
5
6
7
8
router.route('/login').post(passport.authenticate('local',
    {
        successRedirect : '/',
        failureRedirect : '/login'
        // 실패했을 때, 플래시 메시지로 오류를 
        failureFlash : true
    }
});
cs

 

 

이러한 오류 메시지는 스트래티지를 설정할 때, 검증 콜백(Verify Callback)이 설정되어 있다면 자동으로 설정된다.

검증 콜백은 인증을 처리하는 함수를 말하는데, 어떤 문제로 인증이 실패했는지 정확하게 알려준다.

검증 콜백은 이후에 알아보도록 한다.

 

만약 플래시 메시지를 직접 응답 웹 문서 쪽으로 전달하고 싶다면, 다음과 같이 구체적으로 메시지를 지정할 수 도 있다.

1
2
3
4
passport.authenticate('local', {failureFlash : 'Invalid username or password.'});
...
passport.authenticate('local', {successFlash : 'Welcome!'});
...
cs

실패했을 때의 메시지는 failureFlash / 성공했을 때의 메시지는 successFlash 속성으로 설정된다.

 

만약 지금까지 설명한 내용만으로 사용자 인증 처리에 필요한 기능을 모두 실행하기 어렵다면, 커스텀 콜백(Custom Callback) 을 사용할 수도 있다.

커스텀 콜백은 인증을 성공했거나 실패했을 때 어떻게 처리할 것인지를 직접 함수로 지정하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
router.route('/login').get(function(req,res,next){
    passport.authenticate('local'function(err,user,info){
        if(err){return next(err);}
        if(!user){return res.redirect('/login');}
        // 패스포트 인증 결과에 따라 로그인 진행
        req.login(user,function(err){
            if(err) {return next(err);}
            return res.redirect('/users/' + user.username);
        });
    })(req,res,next);
});
cs

이 코드에서 authenticate() 메소드가 라우팅 함수로 사용되는 것이 아니라, 다른 라우팅 함수 안에서 호출되도록 설정되어 있다. 이렇게 하면 클로저(Closure)를 통해 req,res 객체에 접근할 수 있다.

 


👉🏻 여기서 잠깐! 클로저가 궁금하다면, 해당 사이트를 참고해보도록 한다.

https://hyunseob.github.io/2016/08/30/javascript-closure/

 

JavaScript 클로저(Closure)

클로저란?MDN에서는 클로저를 다음과 같이 정의하고 있다. 클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 ‘기억한다’. 흔히 함수 내

hyunseob.github.io


 

 

콜백 함수의 파라미터로 err,user,info 객체가 전달된다.

- 예외가 발생하면 err 객체가 설정되어 전달된다.

- 만약, 인증에 실패했다면 user 파라미터에는 false가 설정된다.

- 반대로 인증에 성공하면 user 파라미터에는 사용자 정보가 들어가게 된다.

- info 파라미터에는 스트래티지를 설정할 때 지정한 검증 콜백(Verify Callback)에 의해 제공되는 부가 정보를 담고 있다.

- 콜백 함수에서는 인증 결과를 원하는 대로 다룰 수 있는데, 여기에서 req.login() 메소드는 임의로 만든 함수 예제이다.

- 즉, 어떤 함수를 만들어 두고 이 함수를 호출하여 세션을 만들고 응답을 보내도록 만들 수 있다.

 

인증이 성공하면 패스포트는 일반적으로 로그인 세션을 만든다.

이 세션은 유용하지만, 어떤 경우에는 필요하지 않다.

 

예를 들어 API 기능을 제공하는 서버의 경우 세션을 유지하지 않고 매 요청마다 인증정보를 요구하고 매 요청마다 인증을 진행하게 된다. 이때는 session 옵션을 false로 한다.

1
2
3
4
5
app.get('/api/users/me',
    passport.authenticate('basic', {session:false}),
    function(req,res){
        res.json({id:req.user.id, username:req.user.username});
    });
cs

하지만 일반적으로는 세션을 유지하므로 앞에서 본 코드는 자주 사용하지 않는다는 것을 알아두자.

 

📌 스트래티지 설정과 검증 콜백

스트래티지는 어떻게 설정하는 것일까? 그리고 검증 콜백이라는 것은 무엇일까?

 

인증 방식을 결정하는 스트래티지는 사용자 아이디와 비밀번호를 사용하는 로컬 인증 방식뿐만 아니라 OAuth나 OpenID 같은 인증방식까지 지원할 수 있도록 각각의 인증방식이 정의되어 있다.

보통 클라이언트 요청을 인증하기 전에는 스트래티지가 설정되어 있어야 하며, use() 함수로 설정할 수 있다.

 

가장 처음 살펴볼 인증 방식이 로컬 데이터베이스의 값을 직접 비교하는 로컬 인증 방식이며, 클라이언트에게 전달한 username과 password 파라미터를 사용해 인증한다.

 

local 인증 방식을 진행하기 전에 모듈을 먼저 설치한다. passport-local 모듈을 설치한다.

 

⚙️설치

%npm install passport-local --save

 

다음은 로컬인증방식인 LocalStrategy 객체를 사용하는 전형적인 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var passport = require('passport')
var LocalStrategy = require('passport-local').Strategy;
 
passport.use(new LocalStrategy{
    function(username,password,done){
        UserModel.findOne({username:username}, function(err,user){
            if(err){return done(err);}
            if(!user){
                return done(null,false, {message: 'Incorrect username.'});
            }
            if(!user.validPassword(password)){
                return done(null,false, {message: 'Incorrect password.'});
            }
            return done(null,user);
        });
    }
});
cs

- passport 객체인 use() 메소드를 사용하면 스트래티지를 설정할 수 있다.

- 여기에서는 로컬 인증 방식으로 설정하기 위해서, LocalStrategy 를 사용했고 콜백 함수로 전달되는 username 과 password 파라미터는 클라이언트로부터 전달받은 요청 파라미터이다.

- 콜백 함수 안에서는 데이터베이스 모델 객체를 사용해 일치하는 데이터가 있는지 찾아본다. 

- 그런 다음에 검증 콜백이 사용된다.

 

스트래티지를 설정할 때는 검증 콜백(Verify Callback)에서 인증결과를 처리하게 된다.

앞에서 살펴본 코드에서는 인증 결과를 처리하는 콜백함수를 말한다.

이 검증 콜백의 목적은 인증 정보(Credential)들을 가지고 있는 사용자를 찾아내는 것이며 클라이언트가 보내 온 요청 파라미터들을 사용해 사용자를 찾아내는 과정을 처리한다.

 

사용자를 찾아내는 과정이 끝났다면 성공한 경우와 실패한 경우를 나눠서 done 메소드를 호출하여 알려준다.

그래야 authenticate() 메소드를 호출하는 쪽에서 성공인지 실패인지 결과를 받아볼 수 있다.

 

스트래티지 설정과 authenticate() 메소드 호출간의 관계

done 메소드를 호출하는 방식은 몇 가지가 있다.

첫 번째로, 찾아낸 사용자 정보가 유효하다면 검증 콜백은 done 메소드를 호출하여 패스포트에게 인증된 사용자 정보를 제공한다.

return done(null,user);

 

두 번째로, 인증 정보가 유효하지 않으면 (예를들어, password가 맞지 않는 경우) done 메소드를 호출할 때 user 객체 대신, false 를 파라미터로 전달한다.

return done(null,false);

 

세 번째로, 인증에 실패했을 때 실패 원인을 알려주는 info 메시지를 추가로 전달할 수 있다. 이 정보는 사용자가 재시도하도록 플래시 메시지를 보여주는데 사용할 수 있다.

return done(null,false, {message:'비밀번호가 맞지 않습니다.'});

 

네 번째로, 인증 정보를 검증하는 과정에서 예외가 발생하면 (예를들어, 데이터베이스가 연결되지 않은 경우) done 메소드는 error 객체를 파라미터로 전달하면서 호출되어야한다.

return done(err);

 

로컬 인증 방식에서 검증 콜백의 사용

결국 done() 메소드가 어떻게 호출될지 설정하기에 따라 passport.authenticate() 메소드를 호출했을 때의 인증 결과가 달라진다는 것이다.

 

패스포트의 기본적인 처리 방식을 알아보았다.

스트래티지를 설정한 후에 passport.authenticate() 메소드를 호출하여 사용자 인증을 진행할 수 있다는 것을 기억하면 실제 코드를 만들어보자.

 

📚 참고

slidesplayer.org/slide/14081172/

 

패스포트로 사용자 인증하기 9장 Do it! Node.js 프로그래밍 이지스퍼블리싱 제공 강의 교안 2017/03 -

어떻게 하면 사용자 인증을 간단하게 할 수 있을까? 강의 주제 및 목차 강의 주제 어떻게 하면 사용자 인증을 간단하게 할 수 있을까? 목 차 1 패스포트로 로그인하기 2 패스포트 관련 코드를 모

slidesplayer.org

 

Comments