소셜 로그인 (OAuth) by Github

‍정진철·2023년 1월 13일
0

Nodejs

목록 보기
9/10

깃헙을 이용한 소셜 로그인

우리의 목표는 사용자를 깃헙으로 보내는 것.
사용자는 깃헙의 아이디와 비밀번호를 입력 후 로그인이 가능해짐.

그리고 우리의 웹사이트에 해당 아이디/비밀번호를 공유하는 것을 승인을 해주고 다시 우리의 웹사이트로 리다이렉트 시켜줌.

이 과정에서 깃헙은 사용자 + token 을 함께 리다이렉트 시킴.

그러면 우리의 웹사이트가 해당 토큰으로 사용자 정보를 가져올 수 있음.
해당 토큰을 매우 빠르게 만료됨.


Github Application 추가




Hompage URL 에는 해당 웹페이지의 주소를 넣고 Authorization callback URL 은


과정

1. 사용자를 깃헙으로 redirect 시킴.

2.

해당 링크에 clinet_id 쿼리를 넣어주면 다음과 같은 페이지로 이동.

3.

접근가능한 데이터가 public data (ex: ID, 프로필 사진 등) 으로 제한됨 우리가 원하는건 이메일!


4.

따라서 아래의 매개변수 중 scope에 내가 알고싶은 내용을 추가시켜야 함.

지정가능한 scope 내용

https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes

    a(href="https://github.com/login/oauth/authorize?client_id=66fb2f3381f68e673bd1&scope=user:email") Continue With Github !

위의 주소처럼 &scopre =user.email을 추가 시켜 주면 이전과는 다르게 private 한 유저의 이메일에도 접근이 가능하다는 걸 알 수 있다.

5.

하지만 위와 같은 url은 지저분하므로 url을 정리해주고 해당 url 클릭 시 redirect 시켜주는 라우터와 컨트롤러를 만들어보자

a(href="/users/github/start") Continue With Github !

이와 같이 정리해주고,,

라우터 설정

컨트롤러 설정

결과

유저의 이메일과 정보를 읽을 수 있게 됨.


redirect URL

유저가 확인을 누르면 보낼 url을 설정 해준다.

access - token 받기

  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 사용

fetch는 유저 단에서만 사용 가능하므로 서버 단에서 사용하기 위해서 module 설치가 필요하다.

npm i node-fetch@2 설치..

그 후 url을 refresh 시켜보면 다음과 같은 access_token의 정보를 확인 할 수 있다.

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 를 추가해 유저의 이메일 정보를 가져온다.

모든것은 우리가 최초에 지정한 scope에 대한 결과이며 해당 결과를 가져와주는 건 access-token을 얻어왔기 떄문이다.


상황에 따른 유저 처리

  1. 해당 유저 이메일 속성 중 primary,verified 속성 모두 true 일 때
    -> 로그인 시켜줌.
      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('/')
   

그리고 결과적으로 데이터베이스에 해당 이메일을 가진 유저가 존재한다면 다음과 같은 결과를 볼 수 있음.


로그인 과정 최종 정리

1. github 로그인 페이지로 넘어가기

-> config를 parameter 로 사용해 baseUrl을 근간으로 해 URL 생성
-> url을 설정하는 이유는 url이 깃헙에게 무언가 알려줄 수 있기 때문임
(ex: client_id, 얻고자 하는 정보(scope) 등 )
-> 그리고나서 유저를 finalUrl로 보냄 (깃헙)

2. redirect URL 로 옮겨가기

유저가 자신의 정보를 공유할지에 대해 승인을 누르면 우리가 사전에 작성한 redirect url로 유저를 보냄.

localhost:4000/users/github/finish?code=xxxxxxxxxxxxxxx

위의 code는 유저가 승인했다는걸 알려주는 징표.

그럼 위의 메소드가 실행되고 마찬가지로 baseUrl을 근간으로해 config를 Parameter 로 붙여 하나의 URL을 생성한다.

깃헙이 준 code를 이용해 POST 요청을 보내고 우리는 access-token을 수령할 수 있다.

access-token은 깃헙 API와 소통할 때 사용.

엑세스 토큰만 있으면 깃헙 API 메소드를 사용할 수 있게됨.

유저에 관한 전반의 정보

유저의 이메일에 관한 정보

모든 이메일을 찾는것이 아닌, primary 와 verified 속성이 'true'인 것만 탐색.

찾은 이메일이 우리의 웹사이트 데이터베이스에 존재한다면 바로 로그인 시켜주기.

데이터베이스에 없다면, 찾은 이메일로 가입시키기.
socialOnly 가 true이면 웹사이트를 통해 가입한게 아니라 깃헙을 통해 가입한 유저임을 알 수 있음.

이것들이 전부 수행되고 나면 쿠키가 생성됨.

로그아웃 시 해당 세션을 제거하기 위해 다음과 같은 메소드 생성.


profile
WILL is ALL

0개의 댓글