Passport

녜정·2022년 6월 15일
0

Passport는 Node의 인증 모듈입니다. 인증요청을 위한 목적으로 설계되어있습니다. 모듈을 작성할 때 캡슐화는 암묵적 룰므로 Passport는 다른 모든 기능을 어플리케이션에 위임합니다.

요즘 웹에서 인증은 다양한 형태 갖고있습니다. 전통적으로 유저 로그인은 이름과 비밀번호를 포함하고 있습니다. 소셜 네트워킹이 뜨면서 Facebook, twitter와 같은 OAuth 공급을 이용한 싱글 sign-on이 자주 사용하는 인증 방법이 되었습니다. API를 노출하는 서비스에는 액세스를 보호하기 위해 토큰 기반 자격 증명이 필요한 경우가 많습니다.

Passport는 각 응용 프로그램에 고유한 인증 요구 사항이 있다는 것을 인식하고 있습니다. 인증 방식은 각 모듈의 패키지들의 strategies 에서 알고 있습니다. 어플리케이션은 전략을 선택하고 불필요한 의존성을 만들지 않아도 됩니다.

인증과 관련된 복잡성에도 불구하고 코드가 복잡할 필요는 없습니다.

app.post('/login', 
         passport.authenticate('local', 
                               { successRedirect: '/', failureRedirect: '/login' }
                              )
        );

Authenticate

요청을 인증하는 것은 passport.authenicate()를 호출하고 사용할 전략을 지정하는 것만큼 간단합니다.authenicate() 함수는 표준 Connect 미들웨어이브로, Express 어플리케이션에서 라우트 미들웨어로 편리하게 사용할 수 있습니다.

app.post('/login',
  passport.authenticate('local'),
  function(req, res) {
    // 이 함수가 호출되면 인증에 성공한 것. `req.user` 인증한 유저에 포함
    res.redirect('/users/' + req.user.username);
  });
  • 인증에 실패하게 된다면, 401 Unathorized로 응답내려주고 추가 경로 처리기는 호출되지 않습니다.
  • 인증 성공 시, 다음 핸들러가 호출되고, req.user 속성이 인증된 사용자로 설정됩니다.

Redirects

리다이렉션은 일반적으로 요청을 인증 후에 일어나는 일입니다.

app.post('/login',
  passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' })
);

이 경우 리다이렉트 옵션이 기본 동작을 재정의합니다. 인증에 성공하면 사용자는 홈페이지로 리다이렉트가 되고, 실패하게 되면 사용자는 다른 시도를 위해 로그인 페이지로 돌아가게 됩니다.

Flash Messages

리다이렉션은 사용자에게 상테 정보를 표시하기위해 플리시 메세지와 결합 됩니다.

app.post('/login',
  passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login', failureFlash: true })
);

failureFlashtrue로 설정하면 콜백이 제공하는 메시지를 사용하여 error메세지를 띄우게 합니다. 확인 콜백이 인증 실패 이류를 가장 정확하게 결정 할 수 있다는 장점이 있습니다.

인증 성공 시, success 메세지도 받을수 있습니다.

passport.authenticate('local', { successFlash: 'Welcome!' });

Disable Sessions(세션 장애)

인증 성공 후에, Passport는 지속 가능한 로그인 세션을 만듭니다. 사용자가 브라우저를 통해 웹 애플리케이션에 액세스하는 일반적인 방법입니다. 하지만, 가끔 세션이 지원되지 않을 수 있습니다. 예를들어, API 서버는 일반적으로 각 요청에 자격 증명을 제공해야합니다. 이 경우 세션 옵션을 false로 설정하여 세션 지원을 비활성화 시킬 수 있습니다.

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

Custom Callback

기본 제공 옵션이 인증 요청을 처리하기에 충분하지 않은 경우 애플리케이션이 성공 또는 실패를 처리할 수 있도록 사용자 정의 콜백을 제공할 수 있습니다.

app.get('/login', 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);
});

autheniticate() 라우트 미들웨어로 사용되지 않고, 라우트 핸들러내에서 호출됩니다. 이것은 closure를 통해 resres 객체에 대한 콜백 액세스를 제공합니다.

인증에 실패한 경우 userfalse로 설정됩니다. 예외가 발생하면, err가 설정됩니다. 선택적으로 info 증명이 통과될 때, 전략의 확인 콜백에서 제공하는 추가 세부 정보를 포함합니다.

콜백은 제공된 인수를 사용하여 원하는 대로 인증 결과를 처리할 수 있습니다.

참고 커스텀 콜백을 사용하면, 세션을 설정하고(req.login()을 호출) 응답을 보내는 것은 어플리케이션의 책임이 됩니다.


Configure

Passport 인증을 사용하기 위해서 3가지 항목을 구성해야합니다.

  1. Authentication strategies : 인증 전략
  2. Application middleware : 어플리케이션 미들웨어
  3. Sessions (optional) : 세션(선택)

Strategies

Passport는 요청을 인증하기 위해 사용하는것입니다. 사용자 이름 및 암호 확인, OAuth를 사용한 위임 인증 또는 Open을 사용한 연합 인증 등 다양한 Strategies의 범위이다.

Passport에 인증 요청하기 전에 어플리케이션에서 사용하는 전략을 구성해야 합니다.

전략, 구성은 use() 함수를 통해 지원됩니다. 예를 들어, username/password 인증에 LocalStrategy 를 사용합니다.

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.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);
    });
  }
));

Verify Callback

이 예시는 중요한 컨셉으로 소개됩니다. Stategies 는 무엇이 verify callback 인지 알아야합니다. verify callback의 목적은 자격 증명 집합을 소유한 사용자를 찾는 것입니다.

Passport 인증 요청 시, 요청에 포함된 자격 증명 구분 분석을 합니다. 그런 다음 해당 자격 증명을 인수로 사용하여 확인 콜백을 호출합니다. (이 경우 usernamepassword). 유요한 자격 증명이라면 인증된 유저에게 Passport를 제공하기 위해 verify callback done 을 내려준다.

return done(null, user);

실패 이유를 나타내기 위해 추가 정보 메시지가 제공될 수 있습니다. 사용자에게 다시 시도하라는 플래시 메시지 를 표시하는데 유용합니다.

return done(null, false, { message: 'Incorrect password.' });

마지막으로 자격증명을 확인하는 동안 예외가 발생하면 기존 Node 스타일의 에러를 done이랑 같이 호출해야합니다.

return done(err);

Note 발생 할 수 있는 두가지 실패 사례를 구별하는 것이 중요합니다. 후자는 errnull이 아닌 값으로 설저되는 서버 예외 사항입니다. 인증 실패는 서버가 정상적으로 작동하는 자연스러운 경우입니다. errnull로 남아 있는지 확인하고 최종 인수를 사용하여 추가 세부 정보를 전달합니다.

이런 방식으로 위임함으로써, verify callback은 Passport 데이터 베이스를 불가지론적(진위여부를 알 수 없다고 보는 철학적 관점)으로 유지합니다.

Middleware

ConnectExpress를 기반으로하는 어플리케이션 안에서 Passport 초기화를 위해 passport.initalize()가 필수적입니다. 어플리케이션이 영구적인 로그인 세션을 사용하는 경우에는 passport.session()을 사용해야합니다.

app.configure(function() {
    app.use(express.static('public'));
    app.use(express.cookieParser());
    app.use(express.bodyParser());
    app.use(express.session({ secret: 'keyboard cat' }));
    app.use(passport.initialize());	//초가화
    app.use(passport.session());	//세션
    app.use(app.router);
});

Note 세션 지원을 활성화하는 것은 선택 사항이지만, 대부분의 응용 프로그램에서 권장 사항입니다. 활성화된 경우 로그인 세션은 올바른 순서로 복원되도록 하려면 passport.session() 전에 session() 을 사용하세요.

Sessions

일반적인 웹 응용 프로그램에서 사용자를 인증하는데 사용되는 자격 증명은 로그인 요청 중에만 전송됩니다. 인증이 성공하면 사용자의 브라우저에 설정된 쿠키를 통해 세션이 설정되고 유지됩니다.

각 후속 요청에는 자격 증명이 포함되지않고 세션을 식별하는 고유한 쿠키가 포함됩니다. 로그인 세션을 지원하기 위해 Passport는 user인스턴스를 세션으로 직렬화 및 역 직렬화합니다.

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

위의 예시에서, 유저 ID만 세션에 직렬화되어 데이터 양을 적게 저장하고 유지합니다. 후속 요청을 받았을 때 ID를 이용해 유저를 찾는데 사용되며 rea.user로 복원됩니다.
직렬화 및 역직렬화 논리는 어플리케이션에서 제공하므로 인증 계층에 의해 부과되지않고 어플리케이션이 적절한 DB나 객체 맵핑을 선택 할 수 있습니다.


Username & Password

웹사이트에서 사용자를 인증하는데 가장 널리 사용되는 방법은 User NamePassword를 사용하는 것입니다. 이 메커니즘에 대한 제원은 passport-local 모듈에서 제공됩니다.

Configuration

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.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);
    });
  }
));

로컬 인증을 위한 verify callback 은 로그인 양식을 통해 어플리케이션에 제출되는 usernamepassword 인수를 허용합니다.

Route

로그인 양식은 POST방식으로 통신합니다. localauthenicate()를 사용하면 로그인 요청이 처리됩니다.

app.post('/login',
  passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login', failureFlash: true })
);

failureFlash 옵션을 true로 설정하면 verify callback에서 설정한 message옵션을 사용하여 error메세지 깜박이게 사용 할 수 있습니다. 이는 사용자에게 다시 시도하라는 메지리를 표시할 때 유용합니다.

Parameters

기본적으로 LocalStrategyusernamepassword라는 매개 변수에서 자격 증명을 찾을 것으로 예상합니다. 사이트에서 이러한 필드의 이름을 다르게 지정하려는 경우 옵션을 사용하여 기본값을 변경 할 수 있습니다.

passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'passwd'
  },
  function(username, password, done) {
    // ...
  }
));

OpenID

OpenID는 연합 인증을 위한 개방형 표준입니다. 웹사이트를 방문할 때 사용자는 OpenID를 제시하여 로그인합니다. 사이트에서 이러한 필드의 이름을 다르게 지정하려는 경우 옵션을 사용하여 기본값을 변경 할 수 있습니다.

OpenIDpassport-openid 모듈에서 제공합니다.

Configuration

OpenID를 사용하는 경우 URL 리턴과 특정 범위를 지정해야합니다. returnURL은 사용자가 OpenID 공급자로 인증한 후 리다이렉션 되는 URL입니다. realm은 인증이 유효한 URL 공간의 부분을 나타냅니다. 일반적으로 웹사이트 루트가 URL이 됩니다.

var passport = require('passport')
  , OpenIDStrategy = require('passport-openid').Strategy;

passport.use(new OpenIDStrategy({
    returnURL: 'http://www.example.com/auth/openid/return',
    realm: 'http://www.example.com/'
  },
  function(identifier, done) {
    User.findOrCreate({ openId: identifier }, function(err, user) {
      done(err, user);
    });
  }
));

OpenID 인증을 위한 verify callback은 사용자가 요청한 식별자가 포함된 식별자 인수를 허용합니다.

Routes

OpenID 인증에는 두 개의 경로가 필요합니다. 첫 번째 경로는 OpenID 식별자가 포함된 양식 제출을 수락합니다. 인증하는 동안 사용자는 OpenID 공급자로 리다이렉션됩니다. 두 번째 경로는 사용자가 OpenID 공급자로 인증한 후 반환되는 URL입니다.

// OpenID 식별자를 수락하고 인증을 위해 사용자를 OpenID 공급자로 리다이렉션.
// 완료되면 공급자는 사용자를 다음 위치의 어플리케이션으로 다시 리다이렉션. 경로는  /auth/openid/return
app.post('/auth/openid', passport.authenticate('openid'));

// OpenID 공급자가 사용자를 응용 프로그램으로 다시 리다이렉션. 
// 인증 절차 완료. 유효한 경우 유저 로그인. 그렇지 않으면 인증에 실패.
app.get('/auth/openid/return', passport.authenticate('openid', { successRedirect: '/', failureRedirect: '/login' }));

Profile Exchange

인증되는 사용자에 대한 프로필 정보를 검색하도록 OPenID를 선택적으로 구성할 수 있습니다. profile 옵션을 true로 설정하면 프로필 교환이 활성화 됩니다.

passport.use(new OpenIDStrategy({
    returnURL: 'http://www.example.com/auth/openid/return',
    realm: 'http://www.example.com/',
    profile: true
  },
  function(identifier, profile, done) {
    // ...
  }
));	

프로필 변경이 활성화되면 verify callback의 서명은 OpenID 공급자가 제공한 사용자 프로필 정보가 포함된 추가 프로필 인수를 수락합니다.


OAuth

OAuth는 사용자가 웹 및 데스크탑, 모바일 등에 대한 API 액세스 권한을 부여할 수 있는 표준 프로토콜입니다. 액세스 권한이 부여도면 승인된 어플리케이션이 사용자를 대신하여 API를 활용 할 수 있습니다. OAuth는 위임된 인증을 위해 등장하였습니다.

OAuth는 두 가지 기본 버전으로 배포합니다.

OAuth 1.0

OAuth 1.0은 여러 단계를 포함하는 위임된 인증 전략입니다. 먼저 요청 토큰을 가져와야 합니다. 그런 다음 사용자는 액세스 권한을 부여하기 위해 서비스 공급자로 리다이렉션됩니다. 마지막으로 권한이 부여된 후 사용자는 어플리케이션으로 다시 리다이렉션되고 요청 토큰을 액세스 토큰으로 교환할 수 있습니다. 소비자로 알려진 액세스를 요청하는 응용 프로그램은 소비자 키와 암호로 식별됩니다.

Configuration

일반 OAuth 전략을 사용할 때 키, 비밀 및 엔드포인트를 옵션으로 지정됩니다.

var passport = require('passport')
  , OAuthStrategy = require('passport-oauth').OAuthStrategy;

passport.use('provider', new OAuthStrategy({
    requestTokenURL: 'https://www.provider.com/oauth/request_token',
    accessTokenURL: 'https://www.provider.com/oauth/access_token',
    userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
    consumerKey: '123-456-789',
    consumerSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(token, tokenSecret, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

OAuth 기반 전략에 대한 확인 콜백은 token,tokenSecret,profile 인수를 허용합니다.token은 액세스 토큰이고, tokenSecret은 해당 암호입니다. profile에는 서비스 제공자가 제공한 사용자 프로플 정보가 포함됩니다.

Routes

OAuth 인증에는 두 개의 경로가 필요합니다. 첫 번째 경로는 OAuth 트랜잭션을 시작하고 사용자를 서비스 공급자로 리디렉션합니다. 두 번째 경로는 공급자 인증 후 사용자가 리디렉션되는 URL입니다.

// 인증을 위해 사용자를 OAuth 공급자로 리다이렉션합니다. 완료되면 공급자는 사용자를 애플리케이션으로 다시 리다이렉션합니다.
//     /auth/provider/callback
app.get('/auth/provider', passport.authenticate('provider'));

// OAuth 공급자가 사용자를 어플리케이션으로 다시 리다이렉션했습니다.
// 액세스 토큰을 얻으려고 시도하여 인증 프로세스를 완료하십시오.
// 권한이 부여되면 로그인되고, 안되면 인증에 실패합니다.
app.get('/auth/provider/callback',
  passport.authenticate('provider', { successRedirect: '/',
                                      failureRedirect: '/login' }
                       )
 );

OAuth 2.0

OAuth 2.0은 OAuth 1.0의 후속 제품으로 이전 버전에서 감지된 단점을 극복하도록 설계되었습니다. 인증 흐름은 기본적으로 동일합니다. 사용자는 먼저 액세스 권한을 부여하기 위해 서비스 공급자로 리디렉션됩니다. 권한이 부여된 후 사용자는 액세스 토큰과 교환할 수 있는 코드를 사용하여 애플리케이션으로 다시 리디렉션됩니다. 클라이언트라고 하는 액세스를 요청하는 애플리케이션은 ID와 비밀번호로 식별됩니다.

Configuration

일반 OAuth 2.0 전략을 사용할 때 클라이언트 ID, 클라이언트 암호 및 엔드 포인트가 옵션으로 지정됩니다.

var passport = require('passport')
  , OAuth2Strategy = require('passport-oauth').OAuth2Strategy;

passport.use('provider', new OAuth2Strategy({
    authorizationURL: 'https://www.provider.com/oauth2/authorize',
    tokenURL: 'https://www.provider.com/oauth2/token',
    clientID: '123-456-789',
    clientSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(accessToken, refreshToken, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

OAuth 2.0 기반 전략에 대한 확인 콜백은 accessToken,refreshToken,profile 인수를 허용합니다. refreshToken은 새 액세스 토큰을 얻는 데 사용할 수 있으며 공급자가 리프레쉬 토큰을 발급하지 않는 경우 정의되지 않을 수 있습니다. profile에는 서비스 제공자가 제공한 사용자 프로필 정보가 포함됩니다.

Routes

OAuth 2.0 인증에는 두 개의 경로가 필요합니다. 첫 번째 경로는 사용자를 서비스 공급자로 리디렉션합니다. 두 번째 경로는 공급자 인증 후 사용자가 리디렉션되는 URL입니다.

// 인증을 위해 사용자를 OAuth 공급자로 리다이렉션합니다. 완료되면 공급자는 사용자를 애플리케이션으로 다시 리다이렉션합니다.
//     /auth/provider/callback
app.get('/auth/provider', passport.authenticate('provider'));

// OAuth 공급자가 사용자를 어플리케이션으로 다시 리다이렉션했습니다.
// 액세스 토큰을 얻으려고 시도하여 인증 프로세스를 완료하십시오.
// 권한이 부여되면 로그인되고, 안되면 인증에 실패합니다.
app.get('/auth/provider/callback',
  passport.authenticate('provider', { successRedirect: '/',
                                      failureRedirect: '/login' }
                       )
 );

Scope

Auth 2.0을 사용하여 접근을 요청할 때 접근 범위는 scope 옵션에 의해 제어됩니다.

app.get('/auth/provider',
  passport.authenticate('provider', { scope: 'email' })
);

여러 범위를 배열로 지정할 수 있습니다.

app.get('/auth/provider',
  passport.authenticate('provider', { scope: ['email', 'sms'] })
);

범위 옵션의 값은 공급자에 따라 다릅니다.


User Profile

Facebook 또는 Twitter와 같은 타사 서비스를 사용하여 인증할 때 사용자 프로필 정보를 사용할 수 있는 경우가 많습니다. 서비스마다 이 정보를 인코딩하는 방법이 다릅니다. 통합을 쉽게 하기 위해 Passport는 프로필 정보를 가능한 한 정규화합니다.

정규화된 프로필 정보는 [Joseph Smarr][schema-author]가 설정한 연락처 스키마를 따릅니다. 사용 가능한 공통 필드는 다음 표에 요약되어 있습니다.

  • provider {String} : 사용자가 인증한 제공자(페이스북, 트위터 등).
  • id {String} : 서비스 공급자가 생성한 사용자의 고유 식별자입니다.
  • displayName {String} : 화면에 적합한 사용자의 이름입니다.
  • name {Object}
    • familyName {String}
    • givenNAme` {String}
    • middleName {String}
  • emails {Array}[n]
    • value {String} : 실제 이메일 주소
    • type {String} : 이메일 주소 유형(집, 직장 등).
  • photos {Array}[n]
    • value {String} : 이미지 주소 URL
profile
안녕하세요, 4년차 백엔드 개발자입니다. 소통하는 것을 좋아하고, velog에는 주로 짧은 글을 작성합니다.

0개의 댓글