일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 컴퓨터 구조
- 연결자료구조
- Express
- 마이크로명령어
- 프론트엔드
- 리액트
- 후위표기법연산
- 제어유니트
- 후위표기법변환
- 보조저장장치
- pug
- JavaScript
- 자바스크립트
- node.js
- 유튜브 클론코딩
- CPU
- Session
- 자료구조
- 유튜브클론코딩
- 표현식과 문
- cookie
- 제어유닛
- 자바스크립트 배열
- Nodemon
- 모던 자바스크립트 Deep Dive
- react
- 깃허브 로그인
- 컴퓨터 구조론
- mongoose
- MongoDB
- Today
- Total
909 Devlog
[유튜브 클론코딩] GitHub Login 본문
*Node.js, express, MongoDB 환경에서 구현하는 깃허브 로그인 방법입니다.*
프로젝트에 소셜 로그인을 구현해 봅시다.
GitHub는 OAuth 방식을 사용하는데, 다른 소셜로그인(카카오, 구글 등) 또한 같은 방식을 사용하니 이번 포스팅을 보시면 다른 소셜로그인도 구현하실 수 있으실 겁니다.
깃허브 로그인 방식은
- 사용자를 깃허브로 보내고, 로그인하게 됨 (깃허브가 비밀번호, 보안, 이메일 인증 등 모든 것을 처리해 줌)
- 승인되면 사용자는 token과 함께 프로젝트 웹사이트로 돌아옴
- 유저의 토큰으로 깃허브 API에 access 해서 사용자의 정보를 가져옴
이므로, 순서대로 알아봅시다.
1. GitHub Applications
깃허브 세팅 페이지에 들어가서 왼쪽 nav 맨 밑에 Developer settings를 클릭해 들어가 줍니다.
(https://github.com/settings/apps ⬅️ 링크에 들어가시면 됩니다.)
들어가서 왼쪽에 OAuth Apps를 클릭하고 오른쪽에 New OAuth App을 클릭합니다.
name과 description은 원하는 대로 써 주시고, URL은 지금 프로젝트에서 사용하시는 Root URL을 넣고, callback URL은 나중에 설명드릴 테니 일단 위와 같은 방식으로 기억하기 쉽게 넣어줍니다.
1.1 사용자 redirect
위에서 깃허브 로그인 방식을 설명드렸습니다. 그중 1단계인 사용자를 깃허브로 redirect 시키는 작업을 해보겠습니다.
제 로그인 페이지 템플릿인 login.pug에 깃허브 로그인 링크를 만들어 보겠습니다.
링크는 https://github.com/login/oauth/authorize으로 연결시켜야 합니다.
// login.pug
extends base.pug
block content
if errorMessage
span=errorMessage
form(method="POST")
input(name="username", type="text", required, placeholder="Username")
input(name="password", type="password", required, placeholder="Password")
input(type="submit", value="Login")
br
a(href="https://github.com/login/oauth/authorize") Continue with GitHub → // <- 깃허브 로그인 링크
hr
div
span Don't have an account?
a(href="/join") Create one now →
지금 들어가면 not found 페이지가 뜨는데, 우리가 parameter를 보내주지 않아서 오류가 뜨는 것입니다.
파라미터는 client_id, allow_signup 등등이 들어가는데 (깃허브 공식문서에서 확인하실 수 있습니다.)
client_id는 필수 값이니 링크를 수정해 봅시다.
client_id는 우리가 GitHub App을 만들었을 때 뜬 값으로 사람마다 다 다르니 값을 확인하시고 넣어주시면 됩니다.
따라서 저는 링크를 https://github.com/login/oauth/authorize?client_id=ae9755618e3200bc5906으로 수정해 주겠습니다.
// login.pug
...
a(href="https://github.com/login/oauth/authorize?client_id=ae9755618e3200bc5906") Continue with GitHub →
...
이제 다시 페이지에서 깃허브로 로그인 링크를 클릭하면 GitHub로 로그인할 수 있게 되었습니다.
**지금 Authorize(로그인 버튼)을 클릭하지 마세요 이어서 할 과정에서 문제가 생길 수 있습니다.**
적혀있는 문구를 보면, Limited access to your public data라고 적혀있습니다.
예를 들어 ID, 프로필 사진 등 제한된 정보를 가져올 수 있다는 건데, 저는 이메일을 얻고 싶으니 더 많은 정보를 요청해 봅시다.
정보는 URL에 scope를 작성함으로써 요청할 수 있습니다. (깃허브 scope 공식문서에서 더 많은 scope를 보실 수 있습니다.)
링크를 아래와 같이 수정해서 읽기 전용 유저 정보와 유저의 이메일을 받아봅시다.
https://github.com/login/oauth/authorize?client_id=클라이언트아이디&scope=read:user user:email
이제 다시 깃허브 로그인 링크를 클릭해 보시면
personal user data를 요청한다고 알려주는 것을 볼 수 있습니다.
만약 링크에 그냥 user라고 입력하면 사용자의 모든 정보를 요청하게 됩니다. (모든 정보를 요청하면 사용자 입장에서 거부감이 들기 때문에 필요한 요소만 골라서 요청하는 게 좋습니다.)
1.2 callbackURL with token
이제 Authorize(로그인) 버튼을 누르면 http://localhost:4000/users/github/callback?code=(각자 다른 코드)으로 이동하게 됩니다.
이 URL은 아까 GitHub App을 만들 때 입력했던 callback URL입니다.
깃허브 로그인 단계 2였던 유저가 token을 가지고 웹사이트로 돌아옴 단계가 실행된 것입니다.
token을 활용하기 위해 userRouter.js에서 /github/callback 링크를 get 하고 userController.js에서 finishGithubLogin controller를 만들어봅시다.
export const finishGithubLogin = (req, res) => {};
userRouter.get("/github/callback", finishGithubLogin);
토큰을 활용하려면, 깃허브에서 준 코드를 access 토큰으로 바꿔야 합니다.
access 토큰으로 바꾸려면 client_id, client_secret, code 파라미터들과 함께 POST 요청을 보내야 합니다.
client_id는 아까 URL에 쓰셨던 것이고,
code는 callbackURL에 들어가 있습니다.
client_secret은 말 그대로 비밀로 해야 하는 client인데, 절대 프론트에 공개되지 않고 백엔드에만 존재해야 합니다.
Client secrets 옆에 Generate a new client secret 버튼을 눌러 코드를 생성합니다.
코드를 생성하셨으면, 위 파라미터를 이용해서 POST 요청을 보내봅시다.
finishGithubLogin을 다음과 같이 수정합니다.
export const 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,
};
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);
res.send(JSON.stringify(json));
};
저는 client_id와 client_secret을 .env 파일을 이용해 숨겼습니다.
process.env.( )는 .env 파일에 저장한 변수이니, .env 파일을 사용하지 않는 분들은 client_id와 client_secret 부분에 받으신 코드를 ""로 감싸서 제가 작성한 process.env 부분에 그대로 작성하시면 됩니다.
이제 깃허브 로그인 버튼을 누르면
다음과 같이 json 형태의 데이터를 가져올 수 있게 되었습니다.
1.3 Access API
이제 json에서 access_token을 가져올 수 있게 되었으니, 깃허브 API에 access 해서 사용자의 정보를 가져옵시다.
finishGithubLogin controller를 다음과 같이 수정합니다.
export const 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,
};
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");
}
};
드디어 깃허브 로그인 버튼을 누르면, 다음과 같이 유저의 정보를 가져올 수 있게 되었습니다.
그런데, 가끔 이메일이나 다른 정보를 공개하지 않는 유저들이 있습니다.
그런 유저들의 이메일을 가져오기 위해서는 이메일 정보를 따로 가져와야 합니다.
controller에 이메일을 가져오기 위한 코드를 작성하고 이메일을 콘솔에 로그해 봅시다.
const emailData = await (
await fetch("https://api.github.com/user/emails", {
headers: {
Authorization: `token ${access_token}`,
},
})
).json();
console.log(emailData);
그러면 이메일 정보 배열이 출력됩니다.
이 중에서 primary와 verified가 모두 true인 이메일을 찾아야 하는데, 그 코드는 다음과 같습니다.
const emailObj = emailData.find(
(email) => email.primary === true && email.verified === true
);
if (!emailObj) {
return res.redirect("/login");
}
이메일 배열에서 primary와 verified가 모두 true인배열을 찾고, 만약 그런 이메일을 찾을 수 없으면 "/login"으로 리다이렉트 시켜주는 코드입니다.
1.4 계정 중복
그런데 이미 제가 만든 프로젝트에는 로그인 기능이 존재합니다. 만약 유저가 웹사이트에서 따로 만든 계정이랑 같은 이메일로 깃허브를 통해 로그인을 하면 어떻게 할까요?
게정을 통합시키는 방법도 있고, 웹페이지 고유의 로그인 방법으로만 로그인하게 하는 방법 등 여러 가지 방법이 존재합니다.
저는 같은 이메일을 가진 유저가 있다면, 그냥 깃허브 계정으로 로그인할 수 있도록 만들겠습니다.
코드는 다음과 같습니다.
const existingUSer = await UserModel.findOne({ email: emailObj.email });
if (existingUser) {
req.session.loggedIn = true;
req.session.user = existingUser;
return res.redirect("/");
}
계정이 존재할 때의 경우에 대한 코드를 작성했으니, 기존 계정이 없을 때를 위한 else를 작성해 봅시다.
else를 작업하기 전에, userSchema에 socialOnly를 만들어 봅시다. 이 데이터는 유저가 깃허브로 로그인했는지 확인할 때 사용할 수 있는 데이터입니다. 만약 유저가 email로 로그인하려는데 password가 없을 경우, 깃허브 로그인으로 만든 계정인지 확인할 수 있습니다.
또, password 옵션에 required를 false로 바꾸었습니다. 깃허브로 로그인할 경우 password가 필요 없기 때문입니다.
// User.js
...
const userschema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
socialnOnly: { type: Boolean, default: false }, // <- 추가
username: { type: String, required: true, unique: true },
password: { type: String, required: false },
name: { type: String, required: true },
location: String,
});
...
else 문에, 유저 모델을 만들고, session에 유저 데이터를 추가시키는 코드는 다음과 같습니다.
github에서 가져온 유저의 정보를 토대로 웹페이지의 UserModel에 맞는 새로운 계정을 만드는 작업입니다.
...
if (existingUser) {
req.session.loggedIn = true;
req.session.user = existingUser;
return res.redirect("/");
} else {
const user = await UserModel.create({
name: userData.name,
username: userData.login,
email: emailObj.email,
password: "",
socialnOnly: true,
location: userData.location,
});
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
}
...
지금 제 데이터 베이스에 등록되어 있는 유저는 아무도 없습니다.
이 상태에서 깃허브로 로그인하면
깃허브에 등록되어있는 정보로 제 웹사이트에 계정이 생성되었습니다.
'토이 프로젝트 > 유튜브 클론코딩' 카테고리의 다른 글
[유튜브 클론코딩] Webpack (0) | 2023.08.06 |
---|---|
[유튜브 클론코딩] MongoDB 데이터베이스 모델 관계 연결 (0) | 2023.08.04 |
[유튜브 클론코딩] Sessions and Cookies (0) | 2023.08.01 |
[유튜브 클론코딩] mongoDB - CRUD (0) | 2023.07.26 |
[유튜브 클론코딩] mongoDB - 기초부터 연결까지 (0) | 2023.07.24 |