[OAuth 2.0] 2. GitHub 로그인 기능 구현

박재현·2022년 3월 18일
0

🔎 GitHub 로그인 기능 구현

1. 등록

1.1) https://github.com/settings/developers 주소에 가서 Homage URL, Authorization callback URL 을 등록한다.

1.2) Client ID, Client Secret 을 부여 받는다.

Client ID
- Authorization Code를 요청할 때 사용

Client Secret
- Access_token을 발급 받을 때 사용

Homepage URL
- Authorization Code를 요청할 주소

Authorization callback URL
- Authorization Code를 전달해줄 주소


2. 코드 구현

💡 Client

1. 로그인 버튼 클릭 시 Authorization Code를 응답해주는 깃허브 주소에 client_id 정보를 담아 전달해준다.

import React, { Component } from 'react';

class Login extends Component {
  constructor(props) {
    super(props)

   	this.socialLoginHandler = this.socialLoginHandler.bind(this)


    this.GITHUB_LOGIN_URL = 'https://github.com/login/oauth/authorize?client_id=93b79c99ac9bd92c9893'
  }
 
  socialLoginHandler() {
    // 1. 해당 URL로 client_id 정보를 담아 해당 페이지로 이동 한다.
    window.location.assign(this.GITHUB_LOGIN_URL)
    /**
     * 2. 등록된 client_id가 입력이 되었다면 Resource Server는 
     *  1) 깃허브 로그인이 안된 경우: 로그인 페이지를 제공한다.
     *  2) 깃허브 로그인이 되어있거나, 위 1)에서 로그인을 수행한 경우
     *    : authorization code를 담아 등록된 callback url(http://localhost:3000) 으로 리디렉션한다. 
     */
  }
 
  render() {
    return (
      <div className='loginContainer'>
        OAuth 2.0으로 소셜 로그인을 구현해보세요.
        <img id="logo" alt="logo" src="https://image.flaticon.com/icons/png/512/25/25231.png" />
        <button
          onClick={this.socialLoginHandler}
          className='socialloginBtn'
        >
          Github으로 로그인
          </button>
      </div>
    );
  }
}

export default Login;

2. 전달받은 Authorization Code를 서버에 보내준다.

import React, { Component } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import Login from './components/Login';
import Mypage from './components/Mypage';
import axios from 'axios';
class App extends Component {
  constructor() {
    super();
    this.state = {
      isLogin: false,
      accessToken: ''
      // TODO:
    };
    this.getAccessToken = this.getAccessToken.bind(this);
  }

  async getAccessToken(authorizationCode) {
    // access token을 받아온 후
    //  - 로그인 상태를 true로 변경하고,
    //  - state에 access token을 저장하세요

    /**
     * 5) client에게 authorization code를 body에 담아 보낸 후
     * ( 서버에서)
     * 6) 응답받은 access token을 상태 변경 함수를 통해 mypage에 렌더링 해준다.
     */
    const getAccessToken = await axios.post('http://localhost:8080/callback', 
    { authorizationCode: authorizationCode })
    
    this.setState ({
      isLogin: true,
      accessToken: getAccessToken.data['accessToken']
    })
  }

  /**
   * 3) Resource(authorization) Server에서 callback url(http://localhost:3000)로 리디렉션 시키면서
   *  params에 authorization code가 전달된다.
   * ex) http://localhost:3000/?code=5e52fb85d6a1ed46a51f
   * 
   * 4) getAccessToken 함수에 authorization code를 전달해준다.
   */
  componentDidMount() {
    const url = new URL(window.location.href)
    const authorizationCode = url.searchParams.get('code')
    // console.log(authorizationCode) //519988e0a20431b64d58 
    if (authorizationCode) {
      // authorization server로부터 클라이언트로 리디렉션된 경우, authorization code가 함께 전달됩니다.
      this.getAccessToken(authorizationCode)
    }
  }
 
  render() {
    const { isLogin, accessToken } = this.state;
    return (
      <Router>
        <div className='App'>
          {isLogin ? (
            <Mypage accessToken={accessToken} />
          ) : (
              <Login />
            )}
        </div>
      </Router>
    );
  }
}

export default App;

2. Authorization Code를 포함한 정보를 Resource Server에 보내주고, Access Token을 응답받는다.

이를 클라이언트에 보내준다.

require('dotenv').config();

const clientID = process.env.GITHUB_CLIENT_ID;
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
const axios = require('axios');
 
module.exports = (req, res) => {
  // req의 body로 authorization code가 들어옵니다. console.log를 통해 서버의 터미널창에서 확인해보세요!
  // console.log(req.body); // { authorizationCode: '696515ead02573b47904' }

  /**
   * 1) Resource Server에 client_id, client_secret, code(authorization code)
   *  를 담아 요청을 보낸다.
   * 2) 받아온 response.data(access_token)을 http://localhost:3000 에 응답으로 보내준다.
   *  {
        access_token: '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',
        token_type: 'bearer',
        scope: ''
      }
   */
  axios.post('https://github.com/login/oauth/access_token', {
    client_id:clientID,
    client_secret:clientSecret,
    code:req.body['authorizationCode'], 
  },
  {
    headers: {
      accept: 'application/json'
    }
  }
  )
  .then((response) => {
    console.log(response.data)
    res.status(200).json({ accessToken: response.data['access_token']});
  })

}

3. 전달받은 Access Token을 바탕으로 깃허브에 유저 정보를 요청하고 이를 활용한다.

import React, { Component } from "react";
import axios from 'axios';
const FILL_ME_IN = 'FILL_ME_IN'

class Mypage extends Component {

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      name: '',
      login: '',
      html_url: '',
      public_repos: ''
    }
  }
 
  /**
   * 7. 올바른 access_Token이 존재한다면 https://api.github.com/user 해당 주소의
   *  header에 authorization: `token <access_token>`로 설정하여 요청을 보낸다.
   * 8. 응답받은 data를 바탕으로 상태를 변경해 렌더링 해준다.
   * console.log(getUserInfo.data)
   *  { name: "박재현", login: "parkjh9370", html_url: "https://github.com/parkjh9370", public_repos: 8
   * ...,...,...}
   */
  async getGitHubUserInfo() {
    // TODO: GitHub API를 통해 사용자 정보를 받아오세요.
    // https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user
    const getUserInfo = await axios.get('https://api.github.com/user', {
      headers: { authorization: `token ${this.props.accessToken}` }
    })
    this.setState({
      name:getUserInfo.data['name'],
      login:getUserInfo.data['login'],
      html_url:getUserInfo.data['html_url'],
      public_repos:getUserInfo.data['public_repos']
    })
  }

  // 9. 해당 access_token header에 담아 client(http://localhost:8080/images)에 요청을 보낸다
  async getImages() {
    // TODO : 마찬가지로 액세스 토큰을 이용해 local resource server에서 이미지들을 받아와 주세요.
    // resource 서버에 GET /images 로 요청하세요.
    const getImages = await axios.get('http://localhost:8080/images', {
      headers: { authorization: `token ${this.props.accessToken}` }
    })
    // console.log(getImages.data)
    this.setState({
      images:getImages.data.images
    })

  }

  componentDidMount() {
    this.getGitHubUserInfo()
    this.getImages()
  }

  render() {
    const { accessToken } = this.props

    if (!accessToken) {
      return <div>로그인이 필요합니다</div>
    }

    return (
      <div>
        <div className='mypageContainer'>
          <h3>Mypage</h3>
          <hr />

          <div>안녕하세요. <span className="name" id="name">{this.state.name}</span>! GitHub 로그인이 완료되었습니다.</div>
          <div>
            <div className="item">
              나의 로그인 아이디:
              <span id="login">{this.state.login}</span>
            </div>
            <div className="item">
              나의 GitHub 주소:
              <span id="html_url">{this.state.html_url}</span>
            </div>
            <div className="item">
              나의 public 레포지토리 개수:
              <span id="public_repos">{this.state.public_repos}</span></div>
          </div>

          <div id="images">
            {
              this.state.images.map(el => {
                return <img src={el.blob} />
                }
              )
            }
          </div>
        </div>
      </div >
    );
  }

}

export default Mypage;

0개의 댓글