웹 서비스를 이용하다보면 가장 짜증나는 것이 아이디랑 비번 찾기이다... 왜 항상 내가 쓴 비밀번호는 틀리는지, 항상 내가 생각한 아이디가 없다고 하는지 모르겠다.

비밀번호의 빡침을 그나마 해소시켜주는 것이 바로 소셜 로그인이다. 자주 들어가는 소셜 서비스의 아이디로 간단하게 서비스를 이용할 수 있는 편리한 시스템인 것 같다.

이번에는 Express X Passport.js 로 간단한 소셜 로그인을 구현해 보았다.

Passport.js 소개

Passport.js는 node.js에서 사용하는 인증 미들웨어이다. 다양한 소셜 서비스의 인증을 지원하고 편하게 사용할 수 있다.

Passport.js

자세한 내용은 직접 코드를 작성하면서 소개하겠다.

Login 설계

서버를 만들기 전에 다음의 규칙?순서?생각?에 따라서 진행하도록 하겠다.

  1. Social Login서비스는 Google을 사용한다.
  2. 로그인페이지에서 Google로그인 버튼을 클릭하면 Google 로그인으로 넘어간다.
  3. Google로그인이 완료되면 인증이 완료되며 루트페이지('/')에 접속할 수 있게 된다.
  4. 로그인이 되어 있지 않은 상태에서 서버 내의 모든 페이지('/~')에 접근하면 자동으로 로그인페이지로 이동한다.
  5. 로그인 세션은 1시간으로 제한한다.

필요한 endpoint

  1. GET '/'
    • content를 보여주는 페이지이다. 로그인 인증이 완료되어야만 보여야 한다.
  2. GET '/login'
    • login페이지이다. 구글로그인 버튼이 있어야 한다.
  3. GET '/login/google'
    • Google login으로 넘어가도록 한다.
  4. GET '/login/google/callback'
    • Google login이 성공적으로 완료되면 돌아오는 페이지이다.

필요한 endpoint별 라우트 구성

Express Generater 로 간단히 서버를 만들어보았다.

그리고 우리가 사용할 passport.js를 설치한다.

$ npm install passport

불필요한 부분을 모두 지우고 다음과 같이 필요한 endpoint만 작성해보았다.

app.get('/', function (req, res, next) {
  res.render('index', { title: 'Express' });
});
app.get('/login', function (req, res, next) {
  res.render('login', { title: 'Login' })
});
app.get('/login/google', function (req, res, next) {

});
app.get('/login/google/callback', function (req, res, next) {

});

Code 작성

Google 플랫폼에 나의 앱 등록하기

먼저 Google 플랫폼에 나의 앱을 등록하고 인증 정보를 만들어야 한다. 구글 로그인의 인증 정보를 이용하기 위해서이다.

구글 클라우드 플랫폼 > api및 서비스 > 사용자 인증 정보

로 접속하여 인증정보를 만든다.

Oauth ID를 생성하면 클라이언트 ID와 Secret key를 받을 수 있다. Passport 설정 시 입력해야 한다.

Passport 설정하기

다음의 3단계로 passport 설정이 필요하다.

  1. Authentication strategies
  2. Application middleware
  3. Sessions (optional)

Authentication strategies

우리 서버에서 google로 인증 요청을 보내는 것을 passport가 대신하기 때문에 해당 인증 정보를 사전에 세팅하는 과정이라고 보면 된다. 세팅은 다음과 같은 코드 진행으로 이루어진다.

그 전에 google 인증을 위한 추가 모듈 설치가 필요하다.

$ npm install passport-google-oauth20

그리고 passport를 세팅하는 코드는 다음과 같다.

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
    clientID: GOOGLE_CLIENT_ID,
    clientSecret: GOOGLE_CLIENT_SECRET,
    callbackURL: "http://localhost:3000/login/google/callback"
  },
  function(accessToken, refreshToken, profile, cb) {
    return cb(null, profile);
  }
));

GoogleStarategy 의 인자는 다음과 같다.

  • clientID : 구글 인증 생성시 받았던 클라이언트 ID
  • clientSecret : 구글 인증 생성시 받았던 클라이언트 secret key
  • callbackURL : 구글 인증 생성시 설정했던 redirection URI 그러므로 endpoint로 만든 /login/google/callback 을 적는다.

인증에 대한 확인 콜백함수로는 4개의 인자를 받는다.

  • accessToken : 받아온 인증 토큰
  • refreshToken : 토큰을 리프레시하여 새롭게 받아온 토큰
  • profile : Google에서 보내준 이용자의 프로필 정보
  • cb(err, obj) : session에 저장하는 함수

serializeUser, deserializeUser

여기서 마지막 인자인 callback함수를 실행하면 두 번째 인자로 넣은 정보를 serializeUser 미들웨어로 전달한다.
serializeUser 미들웨어는 전달받은 객체(정보)를 세션에 저장하는 역할을 한다. 그리고 저장한 객체를 deserializeUser 미들웨어로 전달한다.
**deserializeUser 미들웨어는 서버로 들어오는 요청마다 세션 정보가 유효한 지를 검사하는 역할을 한다.
제대로 된 정보가 들어왔으면 req.user 에 그 정보를 저장하고 요청마다 유저 정보를 넘겨준다. 다음의 코드처럼 작성한다.

passport.serializeUser((user, done) => {
  done(null, user); // user객체가 deserializeUser로 전달됨.
});
passport.deserializeUser((user, done) => {
  done(null, user); // 여기의 user가 req.user가 됨
});

Application middleware

설정을 완료한 다음 라우트 설정을 해준다.

  • GET /login/google

    Google 로그인 페이지로 이동시켜야 한다. passport의 authenticate 메소드를 이용하면 된다. 사용 방법은 다음과 같다.

    app.get('/login/google',
      passport.authenticate('google', { scope: ['profile'] })
    );

    scope는 받아오는 정보 중에 profile 정보를 받아온다는 것을 뜻한다. google에 로그인을 하면 다양한 정보가 넘어오는데 그 중에 우리가 필요한 정보를 선택하여 받아올 수 있다.

  • GET /login/google/callback

    Google 인증이 완료된 후의 작업이 필요하다. 앞서 계획했던 것처럼, 인증이 완료되면 루트페이지('/')로 이동하고 실패하면 다시 로그인 페이지로 이동하도록 설정한다.

    app.get('/login/google/callback',
        passport.authenticate('github', {
          failureRedirect: '/login',
          successRedirect: '/'
    }));

    authenticate 옵션으로 failureRedirect와 successRedirect를 설정할 수 있다.

Sessions (optional)

이렇게 세팅을 하면 로그인 버튼을 누르는 순간 Google 로그인 창이 뜨고 로그인이 완료되면 설정했던 Callback URI로 돌아온다. 하지만,

passport.initialize() middleware not in use

라는 에러 창이 뜬다.

이 에러창이 뜨는 이유는 기본적으로 로그인 세션을 유지하여 서버를 이용해야 하기 때문에 passport의 세션을 초기화 하는 과정이 필요하기 때문이다.

덧붙여서 express의 세션 설정도 해주어야 한다. express 4 이상의 버전에서는 session설정을 따로 해주어야 한다.

  • express session 설정

    • 설치
    $ npm install express-session
    • 코드
    const session = require('express-session');
    app.use(session({
      secret: SECRET_CODE,
      cookie: { maxAge: 60 * 60 * 1000 }
      resave: true,
      saveUninitialized: false
    }));

    session > maxAge 속성으로 세션 시간을 제한할 수 있다. (위의 설정된 시간은 1시간)

그런다음 passport의 세션을 설정하고 초기화하는 코드를 작성한다.

app.use(passport.initialize()); // passport 구동
app.use(passport.session()); // 세션 연결

여기까지 했다면 기본적인 설정이 완료 된 것이다. 이제 로그인 인증을 활용하여 인증된 사용자만 접근이 가능한 페이지를 만들 수 있다.

EXAMPLE

인증된 사용자만 접근이 가능한 페이지 만들기

"루트 페이지는 인증된 사용자만 접근이 가능하다"

인증 정보를 활용하여 루트페이지에 인증된 사용자만 접근하도록 만들 수 있다.req.isAuthenticated() 메소드를 사용할 수 있는데 이 메소드는 서버에 요청을 보낸 사용자가 인증이 되어있는 상태인지를 확인하여 'Boolean' 으로 알려준다. 아래와 같이 미들웨어를 만들어서 페이지 요청 전 미들웨어로 꽂아 넣으면 인증된 사용자만 컨텐츠를 볼 수 있게 할 수 있다.

const authenticateUser = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(301).redirect('/login');
  }
};

app.get('/', authenticateUser, function (req, res, next) {
  res.render('index', { title: 'Express' });
});

지금까지 구글 소셜 로그인을 구현해보았다. passport.js를 이용하면 소셜 뿐만 아니라 로컬 로그인의 인증 과정도 쉽게 구현할 수 있다. 마지막으로 지금까지 구현 과정을 전체 코드로 아래에 적어놓겠다.

const app = express();

const session = require('express-session');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

app.use(session({ secret: 'SECRET_CODE', resave: true, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());

passport.use(new GoogleStrategy({
    clientID: CLIENT_ID,
    clientSecret: CLIENT_SCRET,
    callbackURL: "http://localhost:3000/login/google/callback"
  },
  (accessToken, refreshToken, profile, cb) => {
    return cb(null, profile);
  }
));

passport.serializeUser((user, done) => {
  done(null, user);
});
passport.deserializeUser((user, done) => {
  done(null, user);
});

const authenticateUser = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(301).redirect('/login');
  }
};

app.get('/', authenticateUser, (req, res, next) => {
  res.render('index', { title: 'Express' });
});
app.get('/login', (req, res, next)  => {
  res.render('login', { title: 'Login' })
});
app.get('/login/google',
  passport.authenticate('google', { scope: ['profile'] })
  );
app.get('/login/google/callback',
    passport.authenticate('google', {
      failureRedirect: '/login',
      successRedirect: '/'
}));

By Cyrano on SEP 22, 2019.
Thanks to Vanillacoding