이전까지 한 모든 것들은
Github
로 로그인을 하기 위함이었다.
링크를 누르면 온갖 기능들이 실행된다.
http://localhost:4000/users/github/start
알다시피 이링크는 users/github/start
로 가고 있다.
그건 현재 만든 userRouter.js
에 생성한 route
이다.
이 route
는 controller
를 가지고 있다.
그리고 이 controller
의 유일한 역할은 몇몇 configuration parameter
를
가지고 URL
을 만드는 거다.
그래서 baseUrl
과 parameter
들을 연결 시켰다.
export const startGithubLogin = (req, res) => {
const baseUrl = "https://github.com/login/oauth/authorize";
const config = {
client_id: process.env.GH_CLIENT,
allow_signup: false,
scope: "read:user user:email",
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
return res.redirect(finalUrl);
};
그렇게 해서 finalUrl
을 만들고 여기로 user
를 보내주었다.
user
를 Github
을 보낸거다.
URL
을 설정하는 이유는 URL
이 Github
에게 뭔가를 알려줄수 있어서다.
예를 들면 client_id
같은거다. 그래야 Github
이 어떤 어플에 로그인하는지 알수 있다.
회원가입도 마찬가지이다.
예를 들어 어플에 어떤 종류의 user
를 허용 시킬건지 설정 할수 있다.
그리고 scope
에는 user
로 뭘 할건지 설정하면 된다.
원하는 어떤 scope
도 사용 할수 있다. 하지만 해당 scope
으로 요청해야한다.
페이스북이나 구글 같은 웹사이트는 아주 큰 scope
을 요구할때 어플 검증을 받아야 한다.
그게 며칠씩이나 걸릴수 있기에 귀찮다.
반면 Github
로는 프로필을 읽고 user email
에 접근 하는 것외엔 없다.
그래서 Github
에 user
를 보낼거다.
Github
는 비밀번호, 이메일, 보안 관련된 모든 데이터를 가지고 있는데
이 데이터들을 공유하는데 동의를 하면 웹사이트로 되돌아 올거다.
Github
가 /github/finish
라는 URL
로 돌려 보내준다.
userRouter.get("/github/finish", finishGithubLogin);
좀 정확히 말하자면 localhost:4000/users/github/finish
이다.
저 URL
을 어디에도 만들지 않았다. Github.com
웹사이트에서 만들었다.
URL
은 finishGithubLogin
이라는 function
으로 호출 하는거다.
이 function
은 굉장히 크다. 무슨 내용 들이 있는지 살펴 보면
먼저 user
가 Github
에서 돌아오면 이런 URL
에 ?code=xxx
가 덧붙여진 내용을 받는다.
/gihut/finish?code=XXX
이런 식이다.
이 code
는 user
가 승인했다고 Github
가 알려주는 거다.
그리고 나서 baseUrl
과 몇몇 parameter
들을 받고
parameter
들을 URL
의 parameter string
으로 바꿔준다.
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
그리고 다른 URL
을 만든다.
baseUrl
과 config
를 더해서 다른 URL
을 만드는 거다.
그리고 그 URL
로 무엇을 하냐면 POST request
를 보낸다.
이 URL
에는 Github
가 준 code
가 담겨져 있다.
모든 것이 올바르다면 Github
는 access_token
을 준다.
if ("access_token" in tokenRequest) {
const { access_token } = tokenRequest;
const apiUrl = "https://api.github.com";
const userData = await (
await fetch(`${apiUrl}/user`, {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
이 access_token
은 Github API
와 상호작용 할때 쓸거다.
Github API
는 굉장히 크다 Github API
에 어떤 method
를 보내더라도 응답을 보내준다.
access_token
만 있다면 말이다. 그리고 user
프로필을 받기 위해 요청할수 있다.
그 요청은 api.github.com/user
로 갈것이고
access_token
을 보내면 user
데이터를 받을수 있을거다.
이걸 짧게 쓸수 있는데 await fetch
와 await json
이 그거다.
(이전 파트에서 await
를 쓰지 않으면 어떻게 해야 하는지 해 보았다.)
가끔은 user
들이 email
을 보여주지 않을때도 있다.
그렇기 때문에 email API
에게도 요청을 보내줘야 한다.
const emailData = await (
await fetch(`${apiUrl}/user/emails`, {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
그래서 여기에도 똑같은 access_token
으로 요청을 보내주면 email arrary
를 준다.
그리고 그 email arrary
에서 primary
이면서 verified
된 email
을 찾을거다.
const emailObj = emailData.find(
(email) => email.primary === true && email.verified === true
);
if (!emailObj) {
return res.redirect("/login");
}
let user = await User.findOne({ email: emailObj.email });
if (!user) {
user = await User.create({
avatarUrl: userData.avatar_url,
name: userData.name ? userData.name : userData.login,
username: userData.login,
email: emailObj.email,
password: "",
socialOnly: true,
location: userData.location,
});
}
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
} else {
return res.redirect("/login");
}
};
만일 찾지 못한다면 user
를 로그인 페이지로 돌아가게 만들고 나중에는 notification
을 설정해준다.
(그건 나중에 해보도록 한다.)
notification
을 설정하는 이유는 유저한테 Github
로 로그인 했다는걸 알려주기 위해서다.
그런데 Github
에 verification
된 email
이 없으니 믿을수 없는거다.
만일 primary
이면서 verified
된 email
을 찾게 된다면
데이터베이스에서 해당 email
을 찾게 된다면 유저를 로그인 시킬거다.
만일 데이터베이스에서 email
을 찾지 못했을 경우 그 email
로 user
를 만들거다.
해당 email
과 Github
에 보낸 모든 데이터를 가지고 user
를 만든다.
socialOnly
가 true
이면 Github
로 로그인을 통해 만들어진 계정이란 뜻이 된다.
그렇기에 해당 사용자는 password
가 없으므로 login form
을 사용할수 없다.
원한다면 무작위 password
를 만들어 볼수도 있다.
Github
가 무작위 password
를 줄거라고 생각한다.
node_id
가 바로 무작위 password
이다.
node_id: 'U_kgDOBXfSMQ',
원한다면 유저가 비밀번호를 아예 모르게끔 설정 할수 있다. 빈 password
에 socialOnly
는 true
로 만들었다.
그 이유는 postLogin
에서
export const postLogin = async (req, res) => {
const { username, password } = req.body;
const pageTitle = "Login";
const user = await User.findOne({ username, socialOnly: false });
if (!user) {
return res.status(400).render("login", {
pageTitle,
errorMessage: "An account with this username does not exists.",
});
}
user
가 로그인 하는걸 체크할때 socialOnly
가 false
인걸 확인하려는 거다.
왜냐하면 socialOnly
가 false
이면 username
과 password
로만 로그인 할수 있는 유저이고
socialOnly
가 true
라는건 password
가 없다는 소리이다.
중요한 건 여기에서 username
email
같은 걸 전부 쓰고 있기 때문에 뭔가 하나로 통합하고 싶을거다.
그러면 username
필드를 제거해 본다.
username
과 email
이 공존할 필요 없이 email
만 써도 된다.
다음 포인트는 이것들이 전부 실행되면 쿠키가 생긴다는 거다.
if (!user) {
user = await User.create({
avatarUrl: userData.avatar_url,
name: userData.name ? userData.name : userData.login,
username: userData.login,
email: emailObj.email,
password: "",
socialOnly: true,
location: userData.location,
});
}
logout
기능도 구현했다. 정말 단순하다.
export const logout = (req, res) => {
req.session.destroy();
return res.redirect("/");
};
그리고 logoutController
로 logout URL
을 만들었다.
userRouter.get("/logout", logout);
이 controller
는 userController
로부터 온다. session
을 destroy
했고 그 다음 redirect
를 했다.
user
가 redirect
되면 session
이 없어진다. 그러면 유저가 로그아웃 된다.
이런 흐름은 어디에서든 적용이 된다. 물론 페이스북이나 구글 같은 곳은 좀 더 복잡할거다.
왜냐하면 거긴 더 크니 인증 절차 거치고, 기다리고 그러기 때문이다.
그런데 트위터, 카카오톡, 인스타그램은 매우 비슷하다. 물론 client_id
같은 이름이 똑같지 않을 거다.
app_id
일수도 있다. 하지만 방법은 똑같은 거다.
user
를 소셜 웹사이트로 보내면 몇몇 code
와 함께 웹사이트로 돌려보내고
그 code
로 API
에 request
를 보낼수 있다. 이제 user
인증을 끝냈다.
다음 파트에선 user
프로필 부분을 할거다. 그리고 파일에 대해서다 알아 보겠다.
왜냐하면 Github
으로 로그인해서 계정을 만들때 user
에게 avatar URL
을 준다.
그 말인 즉슨 user
한테 avatarUrl
이 있다는 거다.
그런데 소셜 로그인이 아닌 다른 방법으로 계정을 만든 경우를 생각해보면
email
과 password
로 계정은 만든 유저들은 avatarUrl
이 없다.
그래서 프로필 수정을 구현하고 백엔드에 어떻게 파일들을 보낼수 있는지 알아 보겠다.
Github user
뿐만아니라 모든 user
가 avatarUrl
을 가지게 할거다.
비디오를 만들때도 써 먹을수 있을거다. 비디오 파일을 보낼수 있어야 하기 때문이다.
그게 한 가지 기대되는 포인트고 또 다른 하나는 프로필 수정 페이지를 만들어 볼거다.
프로필 수정 페이지에서는 avatar
를 설정할수 있고 원하는 모든 걸 수정 할수 있게 만들거다.
password
도 바꿀 수 있게 한다.
하지만 socialOnly
가 true
인 경우는 제외 할거다.
socialOnly
가 true
이면 password
를 가지고 있지 않으니깐 말이다.