Passport.js 로 소셜 로그인 구현하기

dev park·2020년 6월 10일
11

0. 들어가기 앞서

Passport 는 정말 친절한 라이브러리다. 단 몇 줄의 코드로, 소셜 로그인을 구현할 수 있다. Passport 로 소셜 로그인을 구현하기 위해 아래 명령어로 패키지를 설치한다.

$ npm i express express-session passport passport-facebook passport-google-oauth20 passport-kakao passport-naver

설치하는 패키지들을 간단하게 설명하면

  • express : 웹 서버 구현을 도와주는 프레임워크
  • express-session : express 프레임워크에서 session을 사용하기 위한 라이브러리
  • passport : passport 코어 라이브러리
  • passport-facebook : facebook 로그인에 필요한 라이브러리
  • passport-google-oauth20 : google 로그인에 필요한 라이브러리
  • passport-kakao : kakao 로그인에 필요한 라이브러리
  • passport-naver : naver 로그인에 필요한 라이브러리

1. Express 로 서버 띄우기

// app.js

const express = require('express')
const http = require('http')

const app = express()
const server = http.createServer(app)

const PORT = process.env.PORT || 5000

server.listen(PORT, () => console.log(`Server is runngin on ${PORT}`))

위 코드를 작성하고 실행하면 5000번 포트로 웹 서버가 실행된다.

2. dotenv 설정

소셜 로그인에 필요한 ID, Secret, Redirect URL을 .env 파일에서 관리 할 것이다. 아래 명령어로 dotenv 라이브러리를 설치 해준다.


$ npm i dotenv
// app.js

const dotenv = require('dotenv')
dotenv.config()

위와 같이 설정 해주면, 코드에서 process.env['key'].env 에 있는 값을 접근 할 수 있다.

3. session 설정

필자는 app.js 가 더러워지는 것을 별로 좋아하지 않아, 설정 관련된 코드는 최대한 파일로 분리 시킨다.

// app.js

/**
 * 세션 세팅
 */
const configureSession = require('./config/session')
configureSession(app)

// config/session.js
const session = require('express-session')

module.exports = (app) => {
  app.use(
    session({
      secret: process.env['SESSION_SECRET'],
      cookie: { maxAge: 60 * 60 * 1000 },
      resave: false,
      saveUninitialized: true,
    })
  )
}

세션 만료 기간은 1시간으로 설정 했으며, secret.env 에서 가져온다.

resave 는 세션을 언제나 저장할 지 정하는 값입니다. express-session doc 에서는 이 값을 false 로 하는 것을 권장하고 필요에 따라 true로 설정합니다.

saveUninitialized 는 세션이 저장되기 전에 uninitialized 상태로 미리 만들어서 저장합니다.

4. passport 설정

// app.js

/**
 * passport 세팅
 */
const configurePassport = require('./config/passport')
configurePassport(app)

// config/passport.js
const passport = require('passport')

module.exports = (app) => {
  app.use(passport.initialize())
  app.use(passport.session())

  /**
   * Serialize
   */
  passport.serializeUser((user, done) => {
    done(null, user)
  })

  /**
   * Deserialize
   */
  passport.deserializeUser((user, done) => {
    done(null, user)
  })
}

serializeUser 는 로그인에 성공했을 때 호출된다. user 의 값은, 뒤에서 추가 할 각 전략의 결과 값을 done 으로 보낸 값이다. 보통은 소셜 로그인에 성공한 사용자의 프로필 정보다. serializeUser 에서 done 을 호출하게 되면, session에 사용자 정보다 저장되고, 두 번째 인자로 전달한 user가 deserializeUser로 전달된다.

deserializeUser 는 서버에 요청이 있을 때마다 호출된다. done 의 두 번째 인자로 user를 전달하게 되면 req.user로 user의 값을 접근할 수 있게 된다.

5. 전략 세우기

passport는 LocalStrategy, GoogleStrategy ... 등 다양한 로그인 전략을 제공한다. 여기서는 GoogleStrategy 만 확인하고, 전체 코드는 git 에서 확인할 수 있다.

// config/passport.js
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20').Strategy

module.exports = (app) => {
  
	...

	/**
   * Google Strategy
   */
  passport.use(
    new GoogleStrategy(
      {
        clientID: process.env['GOOGLE_CLIENT_ID'],
        clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
        callbackURL: process.env['GOOGLE_CALLBACK'],
      },
      function (accessToken, refreshToken, profile, done) {
        done(null, profile)
      }
    )
  )
}

다른 소셜 로그인 모두 구글 로그인과 패턴이 비슷하다. 구글 로그인만 한 번 해보면 다른 것들은 힘들지 않게 할 수 있을 것이다. 여기서 done 을 호출하게 되면 두 번째 인자 profile이 serializeUser 로 전달된다.

6. 라우팅

// app.js

...

/**
 * Routing
 */
const authRouter = require('./routes/auth')
app.use('/auth', authRouter)

app.get('/', (req, res) => {
  /**
   * req.user가 있는 경우는 소셜 로그인에 성공한 경우
   * passport에 의해 user가 주입됨 (deserialize 확인)
   */
  if (req.user) {
    res.send(`
        <h3>Login Success</h3>
        <a href="/auth/logout">Logout</a>
        <p>
            ${JSON.stringify(req.user, null, 2)}
        </p>
      `)
  } else {
    res.send(`
        <h3>Node Passport Social Login</h3>
        <a href="/auth/login/google">Login with Google+</a>
        <a href="/auth/login/facebook">Login with Facebook</a>
        <a href="/auth/login/naver">Login with Naver</a>
        <a href="/auth/login/kakao">Login with Kakao</a>
    `)
  }
})

// routes/auth.js
const express = require('express')
const passport = require('passport')
const router = express.Router()

router.get('/login/google', passport.authenticate('google', { scope: ['profile'] }))
router.get(
  '/login/google/callback',
  passport.authenticate('google', { failureRedirect: '/auth/login' }),
  (req, res) => {
    res.redirect('/')
  }
)

module.exports = router

/ 로 접근 했을 때 로그인 및 로그아웃을 테스트 할 수 있는 간단한 HTML 을 전달하고 있다. 여기서 req.user 의 값을 통해 로그인 여부를 확인 할 수 있다.

/login/google 으로 접근하면, passport ㅡ를 거쳐 google login api 를 호출 한다.

/login/google/callback 은 Google Developer 에서 등록한 callback URL 로 성공, 실패 여부에 대한 callback router다.

7. 로그아웃

로그아웃 코드는 매우 간단하다.

// routes/auth.js

...

router.get('/logout', (req, res) => {
    req.logout()
    res.redirect('/') 
})

passport로 부터 설정된 logout 메소드를 호출 해주고, 그 이후에는 서비스에 알맞게 처리 해주면 된다. 여기서 세션이 제대로 지워지지 않는다는 문제가 종종 발생한다고 하는데, 그럴 땐 아래 코드를 적용하면, 세션이 정상적으로 만료되는 것을 확인 할 수 있을 것이다.

// routes/auth.js

...

router.get('/logout', (req, res) => {
  req.session.destroy((err) => {
    req.logout()
    res.redirect('/')
  })
})

이상으로 Express 환경에서 passport.js 라이브러리를 이용하여, 구글 로그인을 하는 방법을 살펴봤다. 카카오, 네이버 등 다양한 소셜 로그인이 있는데, 전반적인 컨셉은 모두 동일하여 따로 다루지는 않았다. 각 Developer t사이트에서 Client IDClient Secrety Key 만 잘 받아 오면 크게 문제 없이 구현 할 수 있을 것이다.

1개의 댓글

comment-user-thumbnail
2022년 8월 7일

안녕하세요. 글 잘읽었습니다. 해당 글에서 logout 메서드와 session 설정은 자체 로그인 즉 소셜로그인이 아닌 자체적으로 만든 로그인에서 적용되는걸로 알고있습니다. 그러면 logout 을 한다해도 자체적으로 만든 session 이랑 쿠키만 삭제가되고 소셜 서비스에서는 로그아웃이 안된걸로 아는데 맞을까요?

답글 달기