목록passport.js (1)

Node.js + passport.js 를 이용한 소셜 로그인 구현 (페이스북, 깃허브, 구글)

기존 [마크다운 에디터 리뉴얼 관련 포스트](https://massivcode.com/8) 에서 1차 리뉴얼 때 사용했던 passport.js 관련 포스트. 로그인 프로세스는 다음과 같다. 1. 로그인 페이지 진입 2. 특정 소셜 로그인 엘리먼트 클릭 (페이스북, 깃허브, 구글) 3. 해당하는 라우트로 이동 및 passport 미들웨어 호출 4. 각 로그인 플랫폼에 대응하는 콜백 라우트 정의 및 진입시 미들웨어 호출 5. 로그인 성공시 처리 ## 1. npm 종속성 --- ```npm { "name": "markdown-editor", "version": "0.0.0", "private": true, "scripts": { "start": "pm2-dev start ecosystem.config.js", "deploy": "npm install && pm2 start ecosystem.config.js --env production" }, "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "~1.18.2", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "ejs": "~2.5.7", "express": "~4.15.5", "express-session": "^1.15.6", "jsonwebtoken": "^8.1.0", "morgan": "~1.9.0", "mysql2": "^1.5.1", "passport": "^0.4.0", "passport-facebook": "^2.1.1", "passport-github": "^1.1.0", "passport-google-oauth": "^1.0.0", "request-ip": "^2.0.2", "sequelize": "^4.28.6", "serve-favicon": "~2.4.5" }, "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-preset-es2015": "^6.24.1" } } ``` ## 2. app.js - passport.js 관련 설정 --- ```javascript "use strict"; const express = require('express'); const path = require('path'); const favicon = require('serve-favicon'); const logger = require('morgan'); const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); const SESSION_SECRET_KEY = require('./config/SecureConfig').SESSION_SECRET_KEY; const session = require('express-session'); const passport = require('passport'); ... const app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json({limit: '50mb'})); app.use(bodyParser.urlencoded({extended: false, limit: '50mb'})); app.use(cookieParser()); app.use('/markdown', express.static(path.join(__dirname, 'public'))); app.use(session({ secret: SESSION_SECRET_KEY, resave: false, saveUninitialized: true })); require('./config/passport')(passport); app.use(passport.initialize()); app.use(passport.session()); //로그인 세션 유지 ... module.exports = app; ``` ## 3. passport.js - passport 설정 모듈 --- ```javascript const FacebookStrategy = require('passport-facebook').Strategy; const GithubStrategy = require('passport-github').Strategy; const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; const userService = require('../service/userService'); module.exports = (passport) => { passport.serializeUser((user, done) => { done(null, user); }); passport.deserializeUser((user, done) => { done(null, user); }); passport.use(new FacebookStrategy({ clientID: "", clientSecret: "", profileFields: ['id', 'displayName', 'photos'], callbackURL: 'http://localhost:3000/markdown/auth/facebook/callback' }, function (accessToken, refreshToken, profile, done) { const socialId = profile.id; const nickname = profile.displayName; const profileImageUrl = profile.photos[0].value; onLoginSuccess('Facebook', socialId, nickname, profileImageUrl, done); } )); passport.use(new GithubStrategy({ clientID: "", clientSecret: "", callbackURL: 'http://localhost:3000/markdown/auth/github/callback' }, function (accessToken, refreshToken, profile, done) { const socialId = profile.id; const nickname = profile.username; const profileImageUrl = profile.photos[0].value; onLoginSuccess('Github', socialId, nickname, profileImageUrl, done); } )); passport.use(new GoogleStrategy({ clientID: "", clientSecret: "", callbackURL: 'http://localhost:3000/markdown/auth/google/callback', scope: ['https://www.googleapis.com/auth/plus.me'] }, function (accessToken, refreshToken, profile, done) { const socialId = profile.id; const nickname = profile.displayName; const profileImageUrl = profile.photos[0].value; onLoginSuccess('Google', socialId, nickname, profileImageUrl, done); } )); function onLoginSuccess(platformName, socialId, nickname, profileImageUrl, done) { userService.findOrCreate(platformName, socialId, nickname, profileImageUrl) .spread((user, created) => { if (user.state === 1) { userService.updateUserToJoinedState(user) .then(result => { done(null, user); }) .catch(err => { done(null, user); }) } else { done(null, user); } }); } }; ``` 클라이언트 ID, 클라이언트 시크릿, 콜백 URL (리다이렉트 URL) 은 로그인 플랫폼에서 발급받은 데이터를 넣어준다. onLoginSuccess 에서 로그인 성공한 사용자 정보를 db 에 넣어준다. 각 콜백 메소드의 역할 및 기초적인 사용법은 [passport.js 문서](http://www.passportjs.org/docs/downloads/html/) 와 [생활코딩](https://opentutorials.org/course/2136/12134) 을 참고하면 된다. ## 4. auth.js - 인증 관련 라우터 --- ```javascript "use strict"; const express = require('express'); const router = express.Router(); const passport = require('passport'); const userService = require('../service/userService'); // 로그인 페이지 진입 router.get('/login', function (req, res) { let redirectUrl = req.query.redirectUrl; if (redirectUrl) { res.cookie("redirectUrl", redirectUrl, { expires: new Date(Date.now() + (60 * 1000 * 2)), httpOnly: true }); } res.render('login', {title: '마크다운 에디터 - 로그인', isLogin: false}); }); // 로그아웃 router.get('/logout', (req, res) => { req.logout(); res.redirect('/markdown'); }); // 페이스북 로그인 시작 router.get('/facebook', passport.authenticate('facebook')); // 페이스북 로그인 결과 콜백 router.get('/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/markdown/auth/login' }), (req, res) => { loginSuccessHandler(req, res); }); // 깃허브 로그인 시작 router.get('/github', passport.authenticate('github')); // 깃허브 로그인 결과 콜백 router.get('/github/callback', passport.authenticate('github', { failureRedirect: '/markdown/auth/login' }), (req, res) => { loginSuccessHandler(req, res); }); // 구글 로그인 시작 router.get('/google', passport.authenticate('google')); // 구글 로그인 결과 콜백 router.get('/google/callback', passport.authenticate('google', { failureRedirect: '/markdown/auth/login' }), (req, res) => { loginSuccessHandler(req, res); }); // 로그인 성공시 처리 function loginSuccessHandler(req, res) { let successRedirectUrl = "/markdown"; if (req.cookies.redirectUrl) { successRedirectUrl = req.cookies.redirectUrl; res.clearCookie("redirectUrl"); } return res.redirect(successRedirectUrl); } module.exports = router; ``` 로그인 성공시 loginSuccessHandler 가 호출되는데, 이렇게 따로 분리해놓은 이유는 로그인 성공 후 로그인 요청시의 URL 로 리다이렉트 해주기 위함이다. 만약 이런 부분이 필요 없고, 나는 무조건 특정 페이지로 이동시키고 싶다면 다음과 같이 코드를 수정한다. ```javascript router.get('/google/callback', passport.authenticate('google', { successRedirect: '/', failureRedirect: '/markdown/auth/login' })); ```

Node.js 2018.01.30 6:15