Backend/NodeJS

[passport] passport req에 따라 다르게 처리하기

mandarinko 2023. 6. 25. 11:28
Bearer 문서 보다가 답답해서 모듈 까보기

회사에서 업무를 하던중 passport bearer 전략을 사용하면서 req에 따라 2가지의 다른 인증 방법을 사용해야 하는 경우가 발생을 했다.

 

요구사항

간단하게는 API서버에 request 하는 하는 주체가 인증서버를 통하고 들어오는 요청이 있고, 인증 서버를 거치지 않고 요청이 들어오는 2가지의 경우가 있는데, 여기서 둘 다 bearer 토큰을 사용하고 있으나, 특정 인증서버를 타고 들어오는 요청과 인증서버를 타지 않는 요청을 구분해서 다른 인증 로직을 구현해야 했다.

 

아래는 passport.org에 나와있는 bearer 사용방법에 대한 설명이다.

// Bearer 전략 등록
passport.use(new BearerStrategy(
  function(token, done) {
    User.findOne({ token: token }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      return done(null, user, { scope: 'all' });
    });
  }
));

// Bearer 전략 사용
app.get('/profile', 
  passport.authenticate('bearer', { session: false }),
  function(req, res) {
    res.json(req.user);
  });

 

new BearerStrategy 하는 부분에서 req에 따라 다른 로직을 이용해야 하는데 BearerStrategy에서 어떻게 req를 받아올 수 있을지 찾아보아야 했다.

그러나 공식문서에도 잘 안나와 있고(내가 못 찾았을 가능성 높음) 시간도 많이 있는 상황이 아니어서 일단 모듈 들어가서 찾아보고 없으면 다른 방법으로 우회해야겠다고 생각했다.

 

해결 방법

아래는 new BearerStrategy를 호출하면 호출되는 함수를 passport 모듈에서 가져와봤다.

function Strategy(options, verify) {
  if (typeof options == 'function') {
    verify = options;
    options = {};
  }
  if (!verify) { throw new TypeError('HTTPBearerStrategy requires a verify callback'); }
  
  passport.Strategy.call(this);
  this.name = 'bearer';
  this._verify = verify;
  this._realm = options.realm || 'Users';
  if (options.scope) {
    this._scope = (Array.isArray(options.scope)) ? options.scope : [ options.scope ];
  }
  this._passReqToCallback = options.passReqToCallback;
}

잘 보면 보인다 구세주가 보인다. options.passReqCallback 뭔가 내가 원하는 기능을 담고 있는 변수명이다.

 

일단 뭔가 req객체를 넘길 수 있을것 같다는 방법은 알았다. 그러면 바로 호출부를 수정해 보자.

passReqCallback를 boolean으로 인지하고 true를 넣은 건 아니고 그냥 넣어봤다. 어차피 옵션 값이니까 true 아니면 false겠지 라는 생각으로

passport.use(new BearerStrategy(
  { passReqToCallback: true }, 
  function(token, done) {
    User.findOne({ token: token }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      return done(null, user, { scope: 'all' });
    });
  }
));

 

그러면 이제 저 req를 어떻게 callback 함수에서 가져와서 쓸까 고민을 해야 한다.

무작정 token 변수 앞에 두고 사용할 수도 없는 노릇이고, 그렇다고 done 뒤에 둘 수도 없다.(사실 done 뒤는 아니라고 생각하기는 했다. 모듈들이 callback 함수는 가장 마지막에 받더라..)

 

역시 모든 정답은 코드 속에 있다. 아래 코드는 우리가 passport를 실질적으로 사용하는 부분에서 호출하는 authenticate 함수이다.

Strategy.prototype.authenticate = function(req) {
...
  if (self._passReqToCallback) {
    this._verify(req, token, verified);
  } else {
    this._verify(token, verified);
  }
};

if(sele._passReqCallback)?? 저 if 조건 안으로 들어가면? varify에 req를 전달한다고 알아차릴 수 있다.

나는 passReqCallback를 true를 주었으니 if문 안에 들어갈 것이고 -> 그러면 저기서 this._verify라고 불리는 function에서 req를 아래처럼 사용할 수 있게 된다.

  function(req, token, done) {
    if (req.headers.조건 === 조건) {
    	1번인증 로직
        return done(null, user);
    } else {
        2번인증 로직
        return done(null, user);
    }
    User.findOne({ token: token }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      return done(null, user, { scope: 'all' });
    });
  }

 

이렇게 passport사용하면서 callback 함수에 req를 가져와서 사용하는 방법에 대해 알 수 있었다.

 

글을 끝내기 전에 마지막으로 하나의 삽질을 더 공유하자면

    if (req.headers.조건 === 조건) {
    	1번인증 로직
        return done(null, user);
    }
    2번인증 로직
    return done(null, user);

위처럼 else를 빼고 사용하게 되면 에러를 만날 수 있다 :)

 

아 추가로 passport 문서에서도 위와 관련된 내용을 찾아 링크를 첨부한다.

passport 공식문서: https://www.passportjs.org/concepts/delegated-authorization/