Passport 라이브러리
개인 프로젝트를 진행하면서 웹사이트의 로컬 로그인/회원가입을 구현했다. 그리고 계정의 인증수단으로 이메일 인증을 추가하여 인증이 된 계정만 웹사이트를 이용할 수 있도록 구현을 했다. 하지만 이는 서비스를 바로 이용하고 싶은 유저들 입장에서는 까다로운 절차였고, 이내 이메일인증으로 회원가입을 하는방법 이외에도 소셜 계정으로 간편하게 로그인을 할 수 있도록 하여 편의성을 제공하는 것이 좋다고 생각했다.
Passport는 인증절차로직을 편하게 작업할 수 있게 도와주는 Node.js의 인증관련 미들웨어다. 진행하던 프로젝트에서는 이미 JWT를 이용한 로컬 로그인/회원가입을 구현했으므로 지금 Passport의 사용목적은 구글, 페이스북의 소셜 로그인을 위한 인증 정보를 세팅하는것이다.
Passport는 인증을 하기 위해 전략이라는 것을 사용한다. 기본적인 전략은 로컬 전략이지만 구글이나 페이스북의 API를 이용한 전략을 추가해서 사용할 수 있다.
따라서 다음과 같이 페이스북과 구글의 인증모듈도 추가로 설치했다.
npm install koa-passport passport-facebook passport-google-oauth20
구글과 페이스북의 전략을 설정하려면 ClientID, ClientSecret, 콜백URL 등을 설정해줘야 하는데 이를 위해서는 각각 개발자 페이지에 접속하여 프로젝트를 생성해야한다.
https://developers.facebook.com/
https://developers.google.com/
설정 - 기본설정
Facebook로그인 - 설정
// lib/passport.js
const passport = require('koa-passport');
const FacebookStrategy = require('passport-facebook').Strategy;
const GoogleStrategy = require('passport-google-oauth20').Strategy;
module.exports = () => {
// 페이스북 Strategy
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_ID, // ClientID
clientSecret: process.env.FACEBOOK_SECRET, // ClientSecret
callbackURL: process.env.SERVER_HOST + '/api/auth/login/facebook/callback', // 콜백URL
profileFields: ['id', 'email', 'displayName'] // 가져오고싶은 필드 설정
}, (accessToken, refreshToken, profile, done) => {
return done(null, profile); // 로그인 성공
}));
// 구글 Strategy
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
callbackURL: process.env.SERVER_HOST + '/api/auth/login/google/callback',
profileFields: ['id', 'email', 'displayName']
}, (accessToken, refreshToken, profile, done) => {
return done(null, profile);
}));
}
첫번째 인자로 받는 객체에는 다음을 설정했다.
콜백함수의 파라미터는 다음과 같다.
위의 과정까지는 무난히 진행할 수 있었는데 라우팅을 하는 과정에서 문제가 복잡해졌다.
페이스북이나 구글 로그인을 한 뒤에 받은 프로필 정보(여기서는 scope를 설정해서 email만 받았다.)를 라우트의 controller에 가져와서 사용해야 했기 때문이다.
구글링을 해본 결과 정상적인 방법이라면 세션을 사용하여 프로필 정보를 세션에 저장한 뒤, controller에서 필요할 때 세션에서 꺼내서 사용하면 되는것이었지만 방구석TV는 애초에 세션을 사용하지 않고 JWT를 사용하기 때문에 세션 말고 다른 방법을 찾고자 했다.
마침내 찾은 방법은 다음과 같이 controller에서 passport.authenticate() 를 리턴해주는것이다. 또한 세션은 사용하지 않을것이므로 false로 설정해주었다.
// api/auth/index.js
const Router = require('koa-router');
const auth = new Router();
// 페이스북 로그인
auth.get('/login/facebook',
passport.authenticate('facebook', {
authType: 'rerequest',
scope: ['email']
})
);
// 페이스북 로그인 성공시
auth.get('/login/facebook/callback', authCtrl.fbLoginCb);
// 구글 로그인
auth.get('/login/google',
passport.authenticate('facebook', {
authType: 'rerequest',
scope: ['email']
})
);
// 구글 로그인 성공시
auth.get('/login/google/callback', authCtrl.ggLoginCb);
// api/auth/auth.controller.js
// 페이스북 로그인 콜백
exports.fbLoginCb = (ctx) => {
return passport.authenticate('facebook', { session: false }, async (err, profile, info) => {
// 계정 조회
let account = await Accounts.findByEmail(profile.emails[0].value);
// 계정이 없다면
if (!account) {
// 계정 생성
account = await Accounts.socialRegister(profile.emails[0].value);
}
// 방이 없을 시 방 생성
if (account.room_id === undefined) {
const room = await Rooms.createRoom(account._id);
// 계정의 room_id 필드 업데이트
await account.update({ 'room_id': room._id });
}
// 토큰 생성
const token = await account.generateToken();
// 토큰을 쿠키에 저장
setTokenToCookie(ctx, token);
// 페이지 리다이렉트
ctx.redirect(process.env.CLIENT_HOST + '/auth/social');
})(ctx);
}
// 구글 로그인 콜백
exports.ggLoginCb = (ctx) => {
return passport.authenticate('google', { session: false }, async (err, profile, info) => {
// 계정 조회
let account = await Accounts.findByEmail(profile.emails[0].value);
// 계정이 없다면
if (!account) {
// 계정 생성
account = await Accounts.socialRegister(profile.emails[0].value);
}
// 방이 없을 시 방 생성
if (account.room_id === undefined) {
const room = await Rooms.createRoom(account._id);
// 계정의 room_id 필드 업데이트
await account.update({ 'room_id': room._id });
}
// 토큰 생성
const token = await account.generateToken();
// 토큰을 쿠키에 저장
setTokenToCookie(ctx, token);
// 페이지 리다이렉트
ctx.redirect(process.env.CLIENT_HOST + '/auth/social');
})(ctx);
}
마지막으로 passport 구동과 설정 적용을 위해 index.js를 다음과 같이 작성해주었다.
// src/index.js
const passport = require('koa-passport');
const passportConfig = require('lib/passport');
...
// passport 구동
app.use(passport.initialize());
// passport 설정
passportConfig();
...