패스트캠퍼스 챌린지 16일차 - 페이스북 소셜 로그인 / 로그아웃 기능 구현
0.공부인증
공부 시작 12 : 28
공부 종료 17 : 54
소셜 로그인 복잡함...
포기하면 편하다지만,,,, 요즘에 소셜 로그인이 안되는 웹이 어딨누...
1. GraphQL [ CH - 16 ]
- 페이스북에서 발표한 새로운 API규격
- type 시스템을 갖추고 있음
- Apollo, Prisma 등 다양한 오픈 소스툴들이 있음
Query
- query는 데이터 요청에 사용됨.
- REST 의 GET과 동일함
Mutation
- 변경에 사용됨.
- REST의 POST, DELETE, UPDATE 등과 같음
2. OAuth 에 대해서
- 유저 진입장벽이 낮아짐.
- 유저 허용 여부에 따라 이메일, 프로필 사진, 닉네임 등 기본 정보를 가져올 수 있음.
- 페이스북 로그인 기능 추가하기
https://developers.facebook.com/
Facebook for Developers
iOS 14에 대비한 파트너 준비 사항: Facebook 광고에 영향을 미칠 Apple iOS 14 요구 사항에 대해 자세히 알아보세요. FACEBOOK으로 빌드하기 Facebook의 추천 플랫폼으로 고객과 소통하고 효율을 높여보세요
developers.facebook.com
에서 개발자 등록을 해야함.
https://github.com/JeongHoJeong/node-oauth-example
GitHub - JeongHoJeong/node-oauth-example
Contribute to JeongHoJeong/node-oauth-example development by creating an account on GitHub.
github.com
에는 패캠 강사분이 제작하신 BoilerPlate(?) 가 있음.
BoilerPlate 를 설명하자면,
우선 main.js 에서 시작.
관련 app은 app.js에 설정되어 있음. (기본적인 틀은 이미 다 짜여져 있음.)
클라이언트에서는 pug가 뷰엔진임.
pug에서는 public/fb.js가 로그인 관여 - 페이스북에서 제공하는 기본 틀을 해당 위치에 옮겨야 함.
페이스북에서 제공하는 자바스크립트 코드는 하기와 같음.
다만 실제로 사용하기 위해서는 수정이 필요하긴 함. 변수 명이라던지, 함수 선언 방법이라던지 등
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '{your-app-id}',
cookie : true,
xfbml : true,
version : '{api-version}'
});
FB.AppEvents.logPageView();
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "https://connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
위의 방법처러하게 되면,
http 로컬 호스트 이기 때문에, 사용에 문제가 발생함.
그래서 ngrok 를 활용해서 가상의 https 서버를 운영할 수 있음.
ngrok는 설치 후에 명령어 가 안먹는 다면, 아래 링크를 확인해서 해결할 수 있음.
https://gist.github.com/jwebcat/ecaac7bc7ee26e01cd4a
Installing ngrok on Mac
Installing ngrok on Mac. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
또한 페이스북 디벨로퍼 페이지에서 각종 세팅을 완료하고, https로 연결까지 완료하면,
window.fbAsyncInit = () => {
FB.init({
appId : APP_CONFIG.FB_APP_ID,
// cookie : true,
// xfbml : true,
version : 'v12.0'
});
document.getElementById('fb-login').addEventListener('click', () => {
console.log("hello")
FB.login(
(response) => {
console.log(response)
},
{scope: 'public_profile,email'}
);
})
};
((d, s, id) => {
const fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
const js = d.createElement(s); js.id = id;
js.src = "https://connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
})(document, 'script', 'facebook-jssdk');
이 코드를 정상적으로 사용할 수 있음.
리스폰스를 찍어보면 하기와 같이 출력됨.
{authResponse: {…}, status: 'connected'}
authResponse:
accessToken: "sdfsdIDQ1c8NLcBAEDtYwuGvTYE8okfDp90qV6qQRUMlT8ZDFDSDAB0WoAaKotFn6L1MpwwAvsQfpq6SXWXMXa0WCYxBCZCZAVgqKxw2k1tPztok25xH8G09Axs8ot9yzqvEaXGAGawmky12LeJy9mX6QHD6CsNTw9u8tsSg0y09jqZC5bb258yfQd5bdrSN1A6urwfuPqIlI5LEz0v0ZCM5ZCEAuXj"
data_access_expiration_time: 1639977358
expiresIn: 6242
graphDomain: "facebook"
signedRequest: "N8_9nXspy6pUj4542th2suZNxao.eyJ1c2VyX2lkIjoiMTE1ODcwMzg0Nzg3MjY4MSIsImNvZGUiOiJBUUJJZmRTUUo0OHRTSkQwUjNSRjNVTzJ1OGcwZ0N2Mmx1N3F0bDlBQkJrRjBRemFmb2ZNX3pkWWJkRFBXaGpvc01rbXMxbDg2RHhSMGdLR3BBWEZTN2tmUzFaalJ2Zm9wbHlwNlhrV0MzaFFVX0VaVnJvTnVLX0pZOEdCOHBOczA5ZmI3V1dPVVhmXy0wTEY2SGZLLWZLTjlSU1F1RUdDSGVUQlZEWjJOX1loMUtvSU1mQ2V0c2UzNXRFRUhyQ2JfY1FTRkJHZ3FRY3JwRU52bVpYYlZTajJJbWFZZFg5cXBVb05jTU10Nk02T2N0TTN5NFZhRm51SmxEMWt2X0R1VGpobTA4ekVaSGZIa2M0aFRkaEw3Z3NGLUlrNlBkMk96N05vSklsSjI1UUZYX2gxeElpaC1LWTF0ZzZoMGd5UWlKY0w1Rl9ZN2tiQzhJaU1ScVpoSFVJQSIsImFsZ29yaXRobSI6IkhNQUMtU0hBMjU2IiwiaXNzdWVkX2F0IjoxNjMyMjAxMzU4fQ"
userID: "1158703847823423581"
[[Prototype]]: Objectstatus: "connected"[[Prototype]]: Object
그래서 이렇게 들어온 코드를 활용할 때,
uesrID 값이 아닌 accessToken을 활용해야 보안이 좋음.
accessToken을 활용하면, 이메일 등 사전에 합의된 정보만 가져올 수 있음.
또한 해당 토큰은 만료시간을 가지고 있어서, 해당 기간 동안 서버에 정보를 요청하여 값을 가져올 수 있음.
fetch(`/users/auth/facebook?access_token=${response.authResponse.accessToken}`)
백엔드로 돌아가서, 해당 라우팅 설정을 진행하자ㅏㅏㅏ.
// @ts-check
const express = require('express')
const { getUserAccessTokenForFacebookAccessToken } = require('../fb')
const router = express.Router()
router.post('/auth/facebook', async (req, res) => {
const { access_token: fbUserAccessToken } = req.query
if (typeof fbUserAccessToken !== 'string') {
res.sendStatus(400)
return
}
const userAccessToken = await getUserAccessTokenForFacebookAccessToken(
fbUserAccessToken
)
res.cookie('access_token', userAccessToken, {
httpOnly: true,
secure: true,
})
res.sendStatus(200)
})
module.exports = router
토큰은 크게 두가지임.
-> 1, 페이스북에 정보를 요청할 때 사용하는 토큰.
-> 2, 서버 - 클라이언트에서 사용하는 토큰.
getUserAccessTokenForFacebookAccessToken 의 로직
async function getUserAccessTokenForFacebookAccessToken(token) {
// TODO: implement it
// 1. 페이스북 토큰으로부터 페이스북 아이디를 얻어내자.
const facebookId = await getFacebookIdFromAccessToken(token)
// 0. 해당 페이스북 아이디에 해당하는 유저가 데이터베이스에 있는 경우
// -> 해당 유저를 찾아내서 액세스 토큰을 돌려주어야 함.
const existingUserId = await getUserIdWithFacebookId(facebookId)
if(existingUserId) return getAccessTokenForUserId(existingUserId)
// 0. 해당 페이스북 아이디에 해당하는 유저가 데이터베이스에 없는 경우
// -> 새로운 유저 엑세스 토큰을 뽑아서 새로운 유저 데이터를 생성
const userId = await createUserWithFacebookIdAndGetId(facebookId)
// 이제 유저 아이디로부터 토큰을 만들어서, 돌려주기만 하면 됨.
return getAccessTokenForUserId(userId)
}
위 함수 실행시 처음으로 실행되는 getFacebookIdFromAccessToken
async function getFacebookIdFromAccessToken(accessToken) {
// TODO: implement the function using Facebook API
// https://developers.facebook.com/docs/facebook-login/access-tokens/#generating-an-app-access-token
// 해당 페이스북 토큰이 유효한지 확인하는 작업이 필요함.
// https://developers.facebook.com/docs/graph-api/reference/v10.0/debug_token
// 앱자체의 엑세스 토큰을 발급 받는 것.
const appAccessTokenReq = await fetch(`https://graph.facebook.com/oauth/access_token?client_id=${FB_APP_ID}&client_secret=${FB_CLIENT_SECRET}&grant_type=client_credentials`)
console.log(appAccessTokenReq)
// 앱 자체의 엑세스 토큰
const appAccessToken = (await appAccessTokenReq.json()).access_token
console.log(appAccessToken)
// 애플리케이션 액세스 토큰을 가져왔음으로, 디버그 토큰 진행
// 앱 자체의 엑세스 토큰과, 사용자의 액세스 토큰
const debugReq = await fetch(`https://graph.facebook.com/debug_token?input_token=${accessToken}&access_token=${appAccessToken}`)
const debugResult = await debugReq.json()
console.log(debugResult)
if(debugResult.data.app_id !== FB_APP_ID){
throw new Error("Not a valid access token")
}
// FB에서 내려준 user_id 정보만 리턴함.
return debugResult.data.user_id
}
위 함수에서는 페이스북에서 직접제공해준 userId 를 받을 수 있음.
이후, 조건은 크게 두가지로 갈림.
-> 1. 유저의 페이스북 아이디가 우리 앱의 DB에 있는 경우
해당 데이터 베이스의 정보를 가져와서 return user.id
async function getUserIdWithFacebookId(facebookId) {
// TODO: implement it
// 데이터 베이스에서 조회하려고 하는 페이스북 아이디가 있는 지 확인.
const users = await getUsersCollection()
const user = users.findOne({
facebookId
})
// 있으면 유저의 아이디를 돌려주고,
if(user) return user.id
// 없으면 언디파인을 돌려주자
return undefined
}
-> 2. 유저의 페이스북 아이디가 우리 앱의 DB에 없는 경우 (상단 로직에 따라서, 없으면 자동으로 내려옴.)
아이디를 생성하기 위해서 uuid를 생성함.
해당 정보와 users 데이터 베이스에 정보를 저장해야함.
async function createUserWithFacebookIdAndGetId(facebookId) {
// TOOD: implement it
const users = await getUsersCollection()
const userId = uuidv4() // 우리 서비스 내의 유저 아이디가 만들어진 것임.
await users.insertOne({
id : userId,
facebookId
})
// 새로운 계정을 만들고, 우리 서비스내의 고유 아이디를 돌려줌.
return userId
}
해당 정보를 리턴하게 되면,
async function getUserIdWithFacebookId(facebookId) {
// TODO: implement it
// 데이터 베이스에서 조회하려고 하는 페이스북 아이디가 있는 지 확인.
const users = await getUsersCollection()
const user = users.findOne({
facebookId
})
// 있으면 유저의 아이디를 돌려주고,
if(user) return user.id
// 없으면 언디파인을 돌려주자
return undefined
}
/**
* @param {string} facebookId
* @returns {Promise<string>}
*/
async function createUserWithFacebookIdAndGetId(facebookId) {
// TOOD: implement it
const users = await getUsersCollection()
const userId = uuidv4() // 우리 서비스 내의 유저 아이디가 만들어진 것임.
await users.insertOne({
id : userId,
facebookId
})
// 새로운 계정을 만들고, 우리 서비스내의 고유 아이디를 돌려줌.
return userId
}
최종적으로 해당 정보를 리턴하면 userId를 돌려받을 수 있다.
그리고 그 정보를 쿠키에 저장을 해주면 된다.
// @ts-check
const express = require('express')
const { getUserAccessTokenForFacebookAccessToken } = require('../fb')
const router = express.Router()
router.post('/auth/facebook', async (req, res) => {
const { access_token: fbUserAccessToken } = req.query
if (typeof fbUserAccessToken !== 'string') {
res.sendStatus(400)
return
}
// JWT 토큰 까지는 생성됨.
const userAccessToken = await getUserAccessTokenForFacebookAccessToken(
fbUserAccessToken
)
res.cookie('access_token', userAccessToken, {
// 아래와 같은 세팅이 있어야 자바스크립트에서 접근이 불가능함.
httpOnly: true,
secure: true,
})
res.sendStatus(200)
})
module.exports = router
그리고 클라이언트에서 서버로 데이터를 요청할때,
해당 정보를 가지고 유저 정보를 조회할 수 있다.
로그아웃하기!(로그인에 비해서 Joonnnaaa 쉬움)
클라이언트 쿠키에서 access_token 을 삭제하고,
새로고침 해주면 됨.
fetch(`/users/auth/facebook?access_token=${response.authResponse.accessToken}`,
{
method : 'POST'
})
.then(() => window.location.reload())
},
페이스북 - 소셜 로그인 기능 구현 끝......
어렵...
환급 챌린지 (16/30)
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스 [직장인 실무교육]
프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.
fastcampus.co.kr
#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는Node.js웹프로그래밍초격차패키지