1) SNS 로그인을 할때는, auth/kakao
라우터로 사용자의 로그인 요청을 받고, 카카오 서버에 로그인 요청을 보낸다. auth/kakao/callback
으로 카카오 서버로부터 로그인 정보를 받고, 사용자에게 로그인 여부를 알려준다.
passport-local과는 다르게, passport.authenticate('kakao')
에서는 두번째 인자로 콜백 함수를 넣지 않는다. (passport-kakao는 req.login을 자체적으로 호출하므로 따로 적을 필요가 없기 때문이다.) 대신, 로그인에 실패했을 때 리다이렉트할 url을 failureRedirect
속성에 적는다. 로그인에 성공했을 경우에는 다음 라우터 함수를 실행하도록 한다.
// routes/auth.js
router.get('/kakao', passport.authenticate('kakao')); // 요청이 들어온다.
router.get('/kakao/callback', passport.authenticate('kakao', {
failureRedirect: '/auth/login', // 로그인에 실패했을 경우 해당 라우터로 이동한다
}), (req, res) => { // 로그인에 성공했을 경우, 다음 라우터가 실행된다
res.redirect(`/${req.user.user_id}`);
});
2) (패스포트 내부에서) callbackURL(=redirect uri)과 clientID를 카카오 서버에 전송하면, 카카오 서버는 인증을 진행하고, callbackURL로 액세스 토큰, 리프레시 토큰, 사용자 정보(profile 객체)를 전송한다. passport는 이걸 캐치해서 verify 콜백함수에 넘겨준다.
// passport/kakaoStrategy.js
const passport = require('passport');
const KakaoStrategy = require('passport-kakao').Strategy;
const User = require('../models/user');
module.exports = () => {
passport.use(new KakaoStrategy({ // 첫번째 인자에는 인증에 필요한 값들을 적어넣는다. (카카오 서버에 전송하여 인증받을 내용.)
callbackURL: '/auth/kakao/callback', // 카카오 디벨로퍼에 적어놓은 redirect uri와 같아야 한다
clientID: process.env.KAKAO_ID, // 내 앱의 REST API
},
async(accessToken, refreshToken, profile, done) => { // 사용자가 유효한지 확인하는 verify 콜백함수
// accessToken과 refreshToken: 인증을 유지시켜주는 토큰
// profile: 사용자 정보 객체
// done(error, user): passport-twitter가 자체적으로 req.login와 serializeUser 호출하여 req.session에 사용자 아이디를 저장한다
const exUser = await User.findOne({where: {sns_id: profile.id, provider:'kakao'}});
done(null, exUser);
}));
};
3) verify 콜백함수에서 done()
함수를 호출한다. done()
함수를 호출하면 로그인 전략을 마친다. 로그인 성공 여부에 따라 failureRedirect
로 리다이렉트되거나 (실패), 다음 라우터 함수가 실행된다 (성공).
// passport/kakaoStrategy.js
// 중략...
module.exports = () => {
passport.use(new KakaoStrategy({
callbackURL: '/auth/kakao/callback',
clientID: process.env.KAKAO_ID,
},
async(accessToken, refreshToken, profile, done) => { // verify 콜백함수
try {
const exUser = await User.findOne({where: {sns_id: profile.id, provider:'kakao'}});
done(null, exUser); // 사용자 정보를 넘긴다
} catch (err){
done(err); // 에러가 발생하면 에러 정보를 넘긴다
}
}));
};
// routes/auth.js
router.get('/kakao', passport.authenticate('kakao'));
router.get('/kakao/callback', passport.authenticate('kakao', {
failureRedirect: '/auth/login',
}), (req, res) => { // kakaoStrategy에서 done함수가 호출되면 다음 라우터 함수가 실행된다
res.redirect(`/${req.user.user_id}`);
});
흐름은 passport-kakao와 똑같다. developer.twitter.com에서 프로젝트와 앱을 만들고 초기 설정을 해주고, 코드는 TwitterStrategy
에서 필요한 변수에 맞춰 작성하면 된다.
다만, 이메일을 수집할 때는 Strategy에 includeEmail
속성을 true로 설정해야 한다.
// routes/auth.js
router.get('/twitter', passport.authenticate('twitter'));
router.get('/twitter/callback', passport.authenticate('twitter', {
failureRedirect: '/auth/login', // 로그인 실패 시 여기로 리다이렉트
}), (req, res) => {
res.redirect(`/${req.user.user_id}`); // 로그인 성공 시
});
// passport/twitterStrategy.js
const passport = require('passport');
const TwitterStrategy = require('passport-twitter').Strategy;
const User = require('../models/user');
module.exports = () => {
passport.use(new TwitterStrategy({
// api key와 api secret key는 노출되면 안되기 때문에 루트 디렉터리의 .env 파일에 넣어두고 사용한다.
consumerKey: process.env.TWITTER_API_KEY,
consumerSecret: process.env.TWITTER_SECRET_KEY,
// callbackURL은 Twitter Developer에서 설정한 callback url과 같아야 한다
callbackURL: '/auth/twitter/callback',
includeEmail: true // 사용자 정보 객체에 이메일이 포함되도록 하는 옵션
}, async(token, tokenSecret, profile, cb) => {
// token과 tokenSecret: 트위터 authentication API
// profile: 사용자 정보가 들어있는 객체
// cb(error, user): passport-twitter가 자체적으로 req.login와 serializeUser 호출하여 req.session에 사용자 아이디를 저장한다
try{
const exUser = await User.findOne({where: {email: profile.emails[0].value}});
if (exUser) {
cb(null, exUser);
} else {
const newUser = await User.create({
email: profile.emails[0].value,
nickname: profile.displayName,
sns_id: profile.id,
provider: 'twitter',
});
cb(null, newUser);
}
} catch (err) {
console.error(err);
cb(err);
}
}));
};
마찬가지로 흐름은 똑같다. developers.facebook.com에서 앱을 만들고 초기설정을 해준다.
email 권한을 가져오기 위해서는 passport.authenticate()
의 두번째 인자로 {scope: 'email'}
을 추가해주어야 하며, FacebookStrategy
생성자에 profileFields: ['id', 'displayName', 'email']
속성으로 어떤 사용자 정보를 가져올지 지정해야 한다.
// routes/auth.js
router.get('/facebook', passport.authenticate('facebook', {scope: 'email'})); // 페이스북 서버에 이메일 권한 요청
router.get('/facebook/callback', passport.authenticate('facebook', {
failureRedirect: '/auth/login',
}), (req, res) => {
res.redirect(`/${req.user.user_id}`);
});
// passport/facebookStrategy.js
const passport = require('passport');
const FacebookStrategy = require('passport-facebook').Strategy;
const User = require('../models/user');
module.exports = () => {
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET_CODE,
callbackURL: '/auth/facebook/callback',
profileFields: ['id', 'displayName', 'email'] //id, 이름, email을 가져올 것이라고 명시
}, async(accessToken, refreshToken, profile, cb) => {
try{
const exUser = await User.findOne({where: {email: profile.emails[0].value, provider: 'facebook'}});
if (exUser) {
cb(null, exUser);
} else {
const newUser = await User.create({
email: profile.emails[0].value,
nickname: profile.displayName,
sns_id: profile.id,
provider: 'facebook',
});
cb(null, newUser);
}
} catch (err) {
console.error(err);
cb(err);
}
}));
};