[Node.js]auth-oauth

이정원·2022년 8월 18일
0

OAuth를 이용하여 github에 소셜로그인을 하는 것을 구현해보았다.

초기설정

github에 들어가서 setting->Developer settings->OAuth Apps으로 가서 OAuth App을 생성해준다.
생성된 OAuth App에서 Client ID 값과 Client secrets값을 .env.example파일에 넣어주고, .env파일로 바꿔준다.

서버 구현

먼저 authorization callback에 대한 GitHub access token요청을 처리하고, token을 받아온 후에 클라이언트로 응답을 전달해줘야한다. test케이스를 보면 메소드 방식이 post로 되어있고 url이 친절하게 나와있다 axios를 활용 하여 promis형식으로 작성해 볼 수 있다.
axios는 전에 포스트했던 게시글에 올라와있는 공식문서를 참조하면 어렵지 않게 작성할 수 있다.

callback.js

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);
axios({
  method: 'post',
  url: `https://github.com/login/oauth/access_token`,
  headers: {
    accept: 'application/json',
  },
  data: {
    client_id: clientID,
    client_secret: clientSecret,
    code: req.body.authorizationCode
  }
})
  .then((data) => {
    const accessToken = data.data.access_token;
    res.status(200).send({accessToken: accessToken})
  })
  .catch((err) => {
    res.status(400)
  })
  // TODO : 이제 authorization code를 이용해 access token을 발급받기 위한 post 요청을 보냅니다. 다음 링크를 참고하세요.
  // https://docs.github.com/en/free-pro-team@latest/developers/apps/identifying-and-authorizing-users-for-github-apps#2-users-are-redirected-back-to-your-site-by-github

}

여기서 .then을 작성해주지 않으면 test케이스가 통과되지 않을 수 있으니 항상 promise형식으로 작성해줘야한다.
.catch는 예외처리로 선택사항이므로 작성하지 않아도 test 케이스 통과에는 문제가 없다.
또한 then에서 함수 선언시 파라미터 값을 res로 주게되면 위의 파라미터로 생성된 res로 인식하여 사용될 수 있으니 이 점을 주의하여 작성해줘야한다.

images.js 는 토큰이 주어지지 않는경우와 주어지는 경우의 따라 조건문을 이용하여 접근권한 부여와 제한을 하는 코드를 작성해주면 된다. 바로 요청헤더값을 조건문에 넣어도 되지만 보기 좋게 변수에 담아서 작성해보았다.
images.js

const images = require('../resources/resources');

module.exports = (req, res) => {
  const accessToken = req.headers.authorization;
  if(!accessToken){
    res.status(403).send({
      message: 'no permission to access resources'
    })
  }else{
    res.status(200).send({ images:
      images
    })
  }
  // TODO : Mypage로부터 access token을 제대로 받아온 것이 맞다면, resource server의 images를 클라이언트로 보내주세요.
}

클라이언트 구현

먼저 App.js를 보면 access token이 상태로 존재해야하고, authorization code가 url parameter로 전달될 경우, access token을 서버에서 받아올 수 있게 코드를 작성해야한다. access token을 상태로 존재하게 하려면 this.state안에 accessToken을 property로 넣어주고, getAccessToken안에 파라미터를 넣어주고 비동기로 axios를 이용하고 promise형식으로 작성하면 된다.
App.js

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) {
    // 받아온 authorization code로 다시 OAuth App에 요청해서 access token을 받을 수 있습니다.
    // access token은 보안 유지가 필요하기 때문에 클라이언트에서 직접 OAuth App에 요청을 하는 방법은 보안에 취약할 수 있습니다.
    // authorization code를 서버로 보내주고 서버에서 access token 요청을 하는 것이 적절합니다.

    // TODO: 서버의 /callback 엔드포인트로 authorization code를 보내주고 access token을 받아옵니다.
    // access token을 받아온 후
    //  - 로그인 상태를 true로 변경하고,
    //  - state에 access token을 저장하세요
    await axios.post("http://localhost:8080/callback", {
      authorizationCode,
    }).then(res => {
    this.setState({
      isLogin: true,
      accessToken: res.data.accessToken,
    })
  })
  }

  componentDidMount() {
    const url = new URL(window.location.href)
    const authorizationCode = url.searchParams.get('code')
    if (authorizationCode) {
      // authorization server로부터 클라이언트로 리디렉션된 경우, authorization code가 함께 전달됩니다.
      // ex) http://localhost:3000/?code=5e52fb85d6a1ed46a51f
      this.getAccessToken(authorizationCode)
    }
  }

  render() {
    const { isLogin, accessToken } = this.state;
    return (
      <Router>
        <div className='App'>
          {isLogin ? (
            <Mypage accessToken={accessToken} />
          ) : (
              <Login />
            )}
        </div>
      </Router>
    );
  }
}

export default App;

사용자 인증링크를 연결해준다.
Login.js

import React, { Component } from 'react';

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

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

    // TODO: GitHub로부터 사용자 인증을 위해 GitHub로 이동해야 합니다. 적절한 URL을 입력하세요.
    // OAuth 인증이 완료되면 authorization code와 함께 callback url로 리디렉션 합니다.
    // 참고: https://docs.github.com/en/free-pro-team@latest/developers/apps/identifying-and-authorizing-users-for-github-apps

    this.GITHUB_LOGIN_URL = 'https://github.com/login/oauth/authorize?client_id=469afefaa9dc0b6e4a15'
  }

  socialLoginHandler() {
    window.location.assign(this.GITHUB_LOGIN_URL)
  }

  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;

API를 통해서 받아올 정보들을 넣어주고, 작성된 정보들을 return문안에서 this.state.이름으로 받아온다.

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


class Mypage extends Component {

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      name: "",
      login: "",
      html_url: "",
      public_repos: 0,
      // TODO: GitHub API 를 통해서 받아올 수 있는 정보들 중에서
      // 이름, login 아이디, repository 주소, public repositoty 개수를 포함한 다양한 정보들을 담아주세요.
    }
  }

  async getGitHubUserInfo() {
    // TODO: GitHub API를 통해 사용자 정보를 받아오세요.
    // https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user
    await axios.get("https://api.github.com/user", {
      headers:{ authorization: `token ${this.props.accessToken}`,
                accept: 'application/json'},
    })
    .then(res => {
    const {name, login, html_url, public_repos} = res.data;
    this.setState({
      name,
      login,
      html_url,
      public_repos
    });
  })
  }

  async getImages() {
    // TODO : 마찬가지로 액세스 토큰을 이용해 local resource server에서 이미지들을 받아와 주세요.
    // resource 서버에 GET /images 로 요청하세요.
    await axios.get("http://localhost:8080/images", {
      headers: { authorization: `token ${this.props.accessToken}`},
    }).then(res => {
    this.setState({
      images: res.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(img => <img key={img.file} src={img.blob}/>)
            }
          </div>
        </div>
      </div >
    );
  }

}

export default Mypage;
profile
Study.log

0개의 댓글