우리의 목표는 사용자를 깃헙으로 보내는 것.
사용자는 깃헙의 아이디와 비밀번호를 입력 후 로그인이 가능해짐.
그리고 우리의 웹사이트에 해당 아이디/비밀번호를 공유하는 것을 승인을 해주고 다시 우리의 웹사이트로 리다이렉트 시켜줌.
그러면 우리의 웹사이트가 해당 토큰으로 사용자 정보를 가져올 수 있음.
해당 토큰을 매우 빠르게 만료됨.
Github Application 추가
해당 링크에 clinet_id 쿼리를 넣어주면 다음과 같은 페이지로 이동.
접근가능한 데이터가 public data (ex: ID, 프로필 사진 등) 으로 제한됨 우리가 원하는건 이메일!
따라서 아래의 매개변수 중 scope에 내가 알고싶은 내용을 추가시켜야 함.
a(href="https://github.com/login/oauth/authorize?client_id=66fb2f3381f68e673bd1&scope=user:email") Continue With Github !
위의 주소처럼 &scopre =user.email을 추가 시켜 주면 이전과는 다르게 private 한 유저의 이메일에도 접근이 가능하다는 걸 알 수 있다.
하지만 위와 같은 url은 지저분하므로 url을 정리해주고 해당 url 클릭 시 redirect 시켜주는 라우터와 컨트롤러를 만들어보자
a(href="/users/github/start") Continue With Github !
이와 같이 정리해주고,,
유저의 이메일과 정보를 읽을 수 있게 됨.
유저가 확인을 누르면 보낼 url을 설정 해준다.
module.exports.finishGtihubLogin = (req,res) => {
const baseUrl = "https://github.com/login/oauth/access_token";
const config = {
client_id: process.env.GH_CLIENT,
client_secret : process.env.GH_SECRET,
code: req.query.code,
}
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const data = await fetch(finalUrl, {
method:"POST",
headers:{
Accept: "application/json"
}
});
const json = await data.json()
console.log(json);
};
다음과 같은 코드 작성.
finalUrl을 가지고 Github 백엔드에 POST요청을 보내면 access-token을 취할 수 있다.
fetch는 유저 단에서만 사용 가능하므로 서버 단에서 사용하기 위해서 module 설치가 필요하다.
npm i node-fetch@2 설치..
그 후 url을 refresh 시켜보면 다음과 같은 access_token의 정보를 확인 할 수 있다.
과정을 한 번 정리하자면,
1) ' 깃헙으로 로그인 하기' 를 클릭한 유저를 깃헙 홈페이지로 보내고, user가 깃헙에서 "예"라고 승인하면 깃헙은 코드를 준다.
2) 해당 코드를 가지고 access-token으로 변환한다.
3) 마지막으로 해당 access-token으로 Githup API를 사용해 user 정보를 가져오게 되는것이다.
module.exports.finishGithubLogin = async (req, res) => {
const baseUrl = "https://github.com/login/oauth/access_token";
const config = {
client_id: process.env.GH_CLIENT,
client_secret: process.env.GH_SECRET,
code: req.query.code,
};
console.log(config);
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
if ("access_token" in tokenRequest) {
const { access_token } = tokenRequest;
const userRequest = await (
await fetch("https://api.github.com/user", {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
console.log(userRequest);
} else {
return res.redirect("/login");
}
};
위의 코드 중 아래 코드를 살펴보자면,
유저가 "yes"를 클릭하게 되면 code가 부여되고 해당 코드를 가지고 access-token을 받게 된다.
그리고 성공적으로 토큰을 받게 된다면, 해당 토큰을 이용해 Github API url로 fetch 해 user정보를 받게된다.
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
if ("access_token" in tokenRequest) {
const { access_token } = tokenRequest;
const userRequest = await (
await fetch("https://api.github.com/user", {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
그러며 다음과 같이 유저에 대한 정보가 반환된다.
현재 우리가 가지고 있는 유저의 email은 null값이므로 추가적으로 요청해 유저의 이메일 정보를 가져와야한다.
아래와 같이 baseUrl을 설정해주고 /emails 를 추가해 유저의 이메일 정보를 가져온다.
const emailObj = emailData.find((email) => email.primary === true && email.verified === true );
if(!emailObj){
return res.redirect("/login");
}
const existingUser = await User.findOne({email : emailObj.email})
if (existingUser) {
req.session.loggedIn = true;
req.session.user = existingUser;
return res.redirect('/')
만약 해당 유저가 입력한 이메일이 존재하지 않는 이메일이라면, 계정을 생성하도록한다.
} else {
// create account.
const user = await User.create({
username : userData.name,
email : emailObj.email,
password :"",
socialOnly:true,
});
req.session.loggedIn = true;
req.session.user = user;
return res.redirect('/')
그리고 결과적으로 데이터베이스에 해당 이메일을 가진 유저가 존재한다면 다음과 같은 결과를 볼 수 있음.
-> config를 parameter 로 사용해 baseUrl을 근간으로 해 URL 생성
-> url을 설정하는 이유는 url이 깃헙에게 무언가 알려줄 수 있기 때문임
(ex: client_id, 얻고자 하는 정보(scope) 등 )
-> 그리고나서 유저를 finalUrl로 보냄 (깃헙)
유저가 자신의 정보를 공유할지에 대해 승인을 누르면 우리가 사전에 작성한 redirect url로 유저를 보냄.
위의 code는 유저가 승인했다는걸 알려주는 징표.
그럼 위의 메소드가 실행되고 마찬가지로 baseUrl을 근간으로해 config를 Parameter 로 붙여 하나의 URL을 생성한다.
깃헙이 준 code를 이용해 POST 요청을 보내고 우리는 access-token을 수령할 수 있다.
엑세스 토큰만 있으면 깃헙 API 메소드를 사용할 수 있게됨.
모든 이메일을 찾는것이 아닌, primary 와 verified 속성이 'true'인 것만 탐색.
찾은 이메일이 우리의 웹사이트 데이터베이스에 존재한다면 바로 로그인 시켜주기.
데이터베이스에 없다면, 찾은 이메일로 가입시키기.
socialOnly 가 true이면 웹사이트를 통해 가입한게 아니라 깃헙을 통해 가입한 유저임을 알 수 있음.
로그아웃 시 해당 세션을 제거하기 위해 다음과 같은 메소드 생성.