Github Login #02

0_CyberLover_0·2022년 4월 18일
0

Node.JS #05

목록 보기
2/19

계속해서 GitHub 로그인 구현을 해보록 한다

이번 파트에서 살펴 볼것은 scope이다.

scope은 유저에게서 얼마나 많이 정보를 읽어내고 어떤 정보를 가져올 것에 대한거다.

그러니 당연히 필요한 정보만 요청 하도록 해야 한다.

https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps

예를 들자면 여기 나와 있는 모든 scope을 보면 여기에서 admin scope을 가져 올수 있다.

무슨 말이냐면 organization을 읽고 쓴다거나 public key에 접근 가능하다.

public key 통째로 관리 할수 있고 public key를 쓸수 있고

public key를 읽을 수 있는 등 아주 다양하다.

혹은 모든 notification에 대해 watch/unwatch에 대한 접근이 가능하다.

user도 마찬가지이다. user의 프로필로 부터 읽고 쓸수도 있다.

뭘 원하는지에 따라 달려있다. write 할 필요가 없다.

그냥 read만 하면 된다. package를 읽을 필요도 없고 gpg_key도 읽을 필요도 없다.

원하는건 user을 읽을 수 있어야 하고 useremail을 가져오는 거다.

원하는건 그게 다이다. read:useruser:email 이다.

그럼 바로 적용해 보도록 한다.

여기 보면 굉장히 긴 URL이 보인다.

block content 
    if errorMessage
        span=errorMessage
    form(method="POST")
        input(placeholder="Username",name="username", type="text", requeired)
        input(placeholder="Password",name="password", type="password", requeired)
        input(type="submit", value="Login")
        br
        a(href="https://github.com/login/oauth/authorize?client_id=2c44ee26963db98266d4&allow_signup=false") Continue with GitHub →

그러니 뭔가 하나를 더 추가해 본다.

permissionscope이라고 부르기도 하는데 여기에 공백으로 구분되는 scope목록을 보내준다.

그럼 예시로 user:email부터 해본다.

block content 
    if errorMessage
        span=errorMessage
    form(method="POST")
        input(placeholder="Username",name="username", type="text", requeired)
        input(placeholder="Password",name="password", type="password", requeired)
        input(type="submit", value="Login")
        br
        a(href="https://github.com/login/oauth/authorize?client_id=2c44ee26963db98266d4&allow_signup=false&scope=user:email") Continue with GitHub →

잘 작동 하는지 확인해 보기로 한다. 새로고침하고 Continue with GitHub 클릭해보면

전이랑은 다르게 보인다. 이제 Personal user data라고 명시되어 있다.

아니면 url에서 user라고 쳐본다.

https://github.com/login/oauth/authorize?client_id=2c44ee26963db98266d4&allow_signup=false&scope=user

그러면 이제 모든 Personal data에 접근 가능하다.

읽고 쓰는 등 모든 접근이 가능하다는 거다. 이렇게 까지는 필요 하지 않다.

결국 이 scope parameter에 따라 달라진다는 거다.

이 말인 즉슨 GitHub랑 대화 할때 "난 이런이런 정보를 원해"라고 요구하는것과 같다.

어떤 정보를 요청하느냐에 따라 user는 승인을 받게 되고 GitHub는 정보에 접근 할수 있도록 token을 줄거다.

보다시피 전부 URL에 기반하고 있다.

카카오톡 로그인을 구현할 때에도 마찬가지로 똑같이 해줘야 한다.

카카오톡이 주는 URL을 받아내고 카카오톡이 주는 ID를 집어 넣고 scope을 집어 넣게 된다.

요청하고 싶은 것이 담긴 API 문서가 있을거다. read:user라는 걸 한 번 보도록 한다.

여기 read:user을 집어 넣어 준다.

https://github.com/login/oauth/authorize?client_id=2c44ee26963db98266d4&allow_signup=false&scope=read:user

그러면 적용 된다. Personal user data 라고 나온다.

하나 더 추가해본다. 공백으로 구분하면 된다. user:email을 복사하고

여기에다가 공백과 user:email을 넣어 준다.

https://github.com/login/oauth/authorize?client_id=2c44ee26963db98266d4&allow_signup=false&scope=read:user%20user:email

이렇게 email주소와 프로필 정보를 원한다고 나온다.

Email addresses (read-only), profile information (read-only)

더 많은 걸 요청 할수도 있다. 그리고 이것들은 전부 URL에 공백으로 구분해서 쓰면 된다.

다른 것도 한번 해본다. 이번에는 delete_repo넣어 본다.

https://github.com/login/oauth/authorize?client_id=2c44ee26963db98266d4&allow_signup=false&scope=read:user%20user:email%20delete_repo

공백을 넣어주고 delete_repo라고 넣어준다. 보다시피 delete repo가 추가 되었다.

그러면 유저가 "예/아니오"를 할수 있다.

여기에 " 이 어플리케이션은 어떤 repository든 삭제 할수 있다" 라고 나온다.

This application will be able to delete any repository to which you have admin rights.

이건 별로 좋아 보이진 않다. 예전에도 봤겠지만 이 URL상에 요청하는 모든 것들이 다 들어 있다.

한가지 문제점은 이 긴 URL을 계속해서 변경 해줘야 한다는 거다. 보다시피 너무 길다.

그리고 GitHub로 계속 되는 똑같은 URL을 원한다고 가정 하면

이걸 Join에 넣는다고 하면 이걸 전부 복붙해야 된다.

scope을 하나 바꾼다 해도 여기 저기 또 바꿔줘야 하고 클라이언트 ID가 바뀌면 다른 template에도 바꿔줘야한다.

그래서 한 군데에 전부 모아두는게 좋다. 물론 이걸 이렇게 쓰게 되면 굉장히 복잡하고 혼란스럽게 된다.

이렇게 바꿔준다.

block content 
    if errorMessage
        span=errorMessage
    form(method="POST")
        input(placeholder="Username",name="username", type="text", requeired)
        input(placeholder="Password",name="password", type="password", requeired)
        input(type="submit", value="Login")
        br
        a(href="/users/github/start") Continue with GitHub →

"/users/github/start"이렇게 해준다. 그리고 아직 route가 없으니 새로운 route를 만든다.

user.Router.js에서 /users는 이미 되어 있으니 상관없고 /github를 추가한다.

userRouter.get("/logout", logout);
userRouter.get("/edit", edit);
userRouter.get("/remove", remove);
userRouter.get("/github/start", startGithubLogin);
userRouter.get(":id", see);

그리고 이건 startGithubLogin이라는 controller를 사용한다.

그런데 아직 이 controller를 만들지 않았으니 만들어 준다.

userController.js에서

export const startGithubLogin = (req, res) => {}
export const edit = (req, res) => res.send("Edit User");
export const remove = (req, res) => res.send("Remove User");
export const logout = (req, res) => res.send("Log out");
export const see = (req, res) => res.send("See User");

그리고 이건 userController의 이 위치에 놓을 거다.

그러면 이제 userControllerimport할 수 있다.

userRouter.js에서

import express from "express";
import {
  edit,
  remove,
  logout,
  see,
  startGithubLogin,
} from "../controllers/userController";
const userRouter = express.Router();

userRouter.get("/logout", logout);
userRouter.get("/edit", edit);
userRouter.get("/remove", remove);
userRouter.get("/github/start", startGithubLogin);
userRouter.get(":id", see);

export default userRouter;

import를 해주었다. 이제 URL이 생겼다. 이 URLuserGithub으로

redirect를 시킬건데 이 URL을 좀더 이쁘게 만들어 본다.

user.controller.js에서

export const startGithubLogin = (req, res) => {
  const baseUrl = "https://github.com/login/oauth/authorize";

여기 startGithubLogin으로 와서 뭘 할거냐면 이 URL전부 말고 authorize까지만 넣어준다.

그리고 const clienID를 넣어준다.

export const startGithubLogin = (req, res) => {
  const clientId = "2c44ee26963db98266d4";
  const baseUrl = "https://github.com/login/oauth/authorize";

이제 이것들은 합쳐져 하나의 URL이 될거다.

하지만 그전에 login.pug에서

block content 
    if errorMessage
        span=errorMessage
    form(method="POST")
        input(placeholder="Username",name="username", type="text", requeired)
        input(placeholder="Password",name="password", type="password", requeired)
        input(type="submit", value="Login")
        br
        a(href="/users/github/start") Continue with GitHub →

URL을 이렇게 작성해준다.

다음으로 allow_signup = falseuser:email scope이 필요하다.

export const startGithubLogin = (req, res) => {
  const config = {
    clientId :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  }
  const baseUrl = "https://github.com/login/oauth/authorize";
};

그래서 config 객체를 만들어 준다. clientId를 이 객체 안에다가 넣어준다.

allow_signup = false로 해주고 scope은 몇가지 scope들을 보내준다.

어떤 scope을 사용할거냐면 read:useruser:email이다.

꼭 공백으로 나눠 줘야 한다.

이게 configuration객체이다. 이제 URL로 바꿔 준다.

아니면 이걸 인코딩해서 URL 상에서도 동작 할수 있게 할수도 있다.

그래서 UrlSearchParams라 하는 utility를 써볼거다.

export const startGithubLogin = (req, res) => {
  const config = {
    clientId :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  };
  URLSearchParams 
  const baseUrl = "https://github.com/login/oauth/authorize";
};

브라우저에서 테스트 해 볼수도 있다. 테스트 해 본다. inspect(검사)로 들어가서

console 에서 이 config 객체를 복붙해본다.

 const config = {
    clientId :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  };
undefined
new URLSearchParams(config)
URLSearchParams {}[[Prototype]]: URLSearchParams
new URLSearchParams(config).toString()
'clientId=2c44ee26963db98266d4&allow_signup=false&scope=read%3Auser+user%3Aemail'

new URLSearchParams(config) 을 하면 물론 아무것도 생기지 않는다.

그렇지만 new URLSearchParams(config).toString()으로 연결 가능하다.

그러면 이런 것들을 얻지만 인코딩 되어서 나온다.

이제 다시 돌아가서

export const startGithubLogin = (req, res) => {
  const config = {
    clientId :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  };
  const params = new URLSearchParams(config);
  const baseUrl = "https://github.com/login/oauth/authorize";
};

여기서부턴 뭔가가 작동되지 않을거다. 왜냐하면 clientId variable을 만들어 줬지만

이건 Github가 기다리고 있는것과는 다르다.

Github는 글자 그대로 client_id를 기다리고 있는거다.

그래서 아무리 javascript 상에 있다 하더라도 Github가 원하는 parameter로 전송 해줘야 한다.

export const startGithubLogin = (req, res) => {
  const config = {
    client_id :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  };
  const params = new URLSearchParams(config);
  const baseUrl = "https://github.com/login/oauth/authorize";
};

config도 있다. parameter도 만들었다. base URl도 있다.

이제 뭘 하냐면 여기서 봤듯이

 const config = {
    clientId :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  };
undefined
new URLSearchParams(config)
URLSearchParams {}[[Prototype]]: URLSearchParams
new URLSearchParams(config).toString()
'clientId=2c44ee26963db98266d4&allow_signup=false&scope=read%3Auser+user%3Aemail'

저것들을 전부 URL에 넣어줘야 한다. 다시 돌아가서

export const startGithubLogin = (req, res) => {
  const config = {
    client_id :"2c44ee26963db98266d4",
    allow_signup : false,
    scope: "read:user user:email",
  };
  const params = new URLSearchParams(config).toString();
  const baseUrl = `https://github.com/login/oauth/authorize?${params}`;
  return res.direct(baseUrl);
};

toString()을 쓰고 아래에는 ?를 추가 한다. 그리고 이렇게 query안에 parameter을 넣어준다.

그리고 ${params}라 적어 준다. 이렇게 URL이 완성 되었다.

하지만 그전에 return res.direct()해주고 baseUrl로 가게 해준다.

사실 baseURL로 가면 안된다. baseURl${params}이 부분이니깐

export const startGithubLogin = (req, res) => {
  const baseUrl = "https://github.com/login/oauth/authorize";
  const config = {
    client_id: "2c44ee26963db98266d4",
    allow_signup: false,
    scope: "read:user user:email",
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  return res.redirect(finalUrl);
};

이렇게 수정해줘야지 맞게 된다.finalUrl이라고 해준다. 그럼 이제 userfinalUrl로 가게 된다.

그리고 길고 긴 string을 가지는 것보다 이렇게 적는 편이 훨씬 좋다.

이제 어디서든 이 URluser에게 보낼수 있게 되었고 작동도 잘 된다.

한줄로 길게 link를 만들 필요가 없다. 테스트 해본다.

새로고침하고 Login해주고 Continue with GitHub 클릭해주면 잘 작동 한다.

즉 이 URL을 건드려서 URL을 만든후

userRouter.get("/github/start", startGithubLogin);
export const startGithubLogin = (req, res) => {
  const baseUrl = "https://github.com/login/oauth/authorize";
  const config = {
    client_id: "2c44ee26963db98266d4",
    allow_signup: false,
    scope: "read:user user:email",
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  return res.redirect(finalUrl);
};

여기로 다시 redirect된다. 확인해 보면 Authorization을 요구하고 있고

email주소와 프로필 정보를 읽고 싶다고 요청했다.

한 가지 더 해야 할건 Authorize를 클릭 할수 있게 만드는 거다.

그리고 Authorize를 클릭할 때 URL에 무슨 일이 일어나는지 잘 보도록 한다.

클릭하고 나면
http://localhost:4000/users/github/callback?code=a8619d69b2f8e2dbee8c

으로 다시 redirect시켜준다. 이 URL을 최근에 본게 어디였는지 생각을 해보니

Authorization callback URL 처음 설정 할때 지정한 URl이다.

만일 Githubuser"yes"라고 답한다면 Githubuser를 어플리케이션의

http://localhost:4000/users/github/callback

URL로 보내주게 된다. 그리고 말했다시피 user에게 code를 보내주게 된다.

code=a8619d69b2f8e2dbee8c 이 코드를 말이다.이 코드는 나중에 사용할거다.

다음 파트에서 다뤄 보기로 한다.

왜냐하면 현재 가지고 있는 않는 이 URLcontorller를 만들어줘야 한다.

다행히도 이미 /github/start를 가지고 있다. 하지만 /github/callback이 필요하다.

아니면 이걸 /finish로 바꿔 줄수 있다. 바꿔 주는게 좋을것 같다.

그리고 이렇게 에러가 뜨는건 Cannot GET /users/github/finish

Router/users/github/finish URL이 없기 때문이다.

그리고 다시 새로고침을 해주고 Login에서 Continue with Github로 가면

그럼 이제 더 이상 물어보지 않고 바로 웹사이트로 돌려 보내주고 있다.

왜냐하면 Github"yse"라고 한걸 기억하고 있기 때문에 다시 또 물어볼 필요가 없다.

그러면 이제 /users/github/finish를 만들어 본다.

userRouter.get("/github/start", startGithubLogin);
userRouter.get("/github/finish", finishGithubLogin);
export const finishGithubLogin = (req, res) => {};

export const edit = (req, res) => res.send("Edit User");
export const remove = (req, res) => res.send("Remove User");
export const logout = (req, res) => res.send("Log out");
export const see = (req, res) => res.send("See User");

이번 파트에서 userGithub로 보내줬고 URL을 좀 더 보기 좋게 만들어 줬다.

이제 usercode와 함께 돌려 보내 줄거다.

다음 파트에서는 user정보를 가져올수 있께 code를 사용해 볼거다.

profile
꿈꾸는 개발자

0개의 댓글