OAuth authentication 구현하기(github 로그인)

🐶·2021년 8월 4일
10


(이미지 출처: 코드스테이츠)

이번 스프린트로 OAuth 작동방식을 이해해보았다. 직접 OAuth로 로그인 가능한 어플리케이션을 제작해보면서 "브라우저" - "내 서버" - "인증 대행 서비스"간 요청/응답을 주고받는 로직을 구현하였다.

깃헙에 내 앱 등록

참고문서를 보면서 깃헙에 내 앱을 등록하였다.

여기서 Authorization callback URL를 입력하는데 이건 무엇이냐면,,,
OAuth 메커니즘이 인증 과정이 끝난 후 리디렉션을 통해 다시 내 앱으로 이동하는 원리이므로, 내 앱으로 돌아가기 위한 Authorization callback URL이 필요하다. Client ID 및 Client Secret 를 확인할 수 있다.

Server

환경설정

GitHub App에서 제공하는 Client ID 및 Client Secret의 정보를 기입한다.

주의: Client Secret은 항상 비밀로 지켜져야 한다!

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

최상단 index.js 파일

이전 session과 token 스프린트와 달리 https가 아니라 http 프로토콜을 사용한다. 따라서 이번 스프린트는 인증서 파일들도 심지 않았다.

controller

아래와 같은 요청을 처리하는 코드를 작성하였다.

1. /controller/callback.js (POST /callback)

Access token을 발급 받는 과정은 클라이언트에서보다, 서버에서 이루어지는 것이 더욱 안전하다. 그래서 클라이언트에서 받아온 Authorization code를 서버의 /callback 엔드포인트로 전달해 서버에서 github App에게 Access token 발급을 요청한다.

깃헙공식문서 를 참고하였다.

axios({
    method: 'post',
    url: `https://github.com/login/oauth/access_token`,
    headers: {
      accept: 'application/json',
    }, //이 부분을 작성하지 않으면 객체형태로 응답을 받지 못한다.(이 부분 작성하지 않은 result를 콘솔로 찍어보니 그냥 문자열 형태...)
    data: {
      client_id: clientID,
      client_secret: clientSecret,
      code: req.body.authorizationCode 
      //클라이언트가 보낸 POST요청 내 body에서 받아온 authorization code를 가지고 
      //authorization server에 access token을 달라고 POST요청을 보낸다
    }
  })

2. /controller/images.js (GET /images)

받아온 Access token을 확인한 후, local에 저장된 resource images를 클라이언트로 보내주는 라우터이다.

여기서 중요한것...!!! 토큰은 클라이언트의 요청 내 헤더에서 담겨서 온다!

const accessToken = req.headers.authorization; 
//토큰은 클라이언트의 요청 내 헤더에서 담겨서 온다! 
  if(!accessToken){
    res.status(403).send({
      message: 'no permission to access resources'
    });
  } else{
    res.status(200).json({
      images
    })
  }

Client

App.js에는 두 개의 컴포넌트가 있다. 현재 상태가 로그인이 되어있다면 Mypage 컴포넌트가, 되어있지 않다면 Login 컴포넌트가 실행된다.

앱을 실행했을 경우 첫 화면의 즉, Login 컴포넌트는 이런 모습이다.

Login 컴포넌트

여기서 'Github으로 로그인' 이라는 버튼을 클릭하게 되면 내가 저장해 놓은 특정 url로 이동하게 된다.

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

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

    // TODO: GitHub로부터 사용자 인증을 위해 GitHub로 이동해야 합니다. 적절한 URL을 입력하세요.
    // OAuth 인증이 완료되면 authorization code와 함께 callback url로 리디렉션 합니다.
    
    this.GITHUB_LOGIN_URL = 'https://github.com/login/oauth/authorize?client_id=ee33270fac53d2e7b61c'
  }

  socialLoginHandler() { //로그인 버튼을 클릭했을 때 실행되는 이벤트핸들러함수
    window.location.assign(this.GITHUB_LOGIN_URL) 
    //location.assign() 메소드: 파라미터로 전달한 URL에서 리소스를 로드한다.
  }

  render() {
//.....

export default Login;

위의 url은 어디서 온 것이냐하면... 아래 깃헙 공식문서에 있는 API이다. 이 url로 GET 요청을 보내게 되면 유저가 자신이 가진 깃헙 계정으로 로그인하게 만든다. 아까 Login 컴포넌트에 있던 'Github으로 로그인' 버튼을 누르자 깃허브의 로그인 페이지로 이동한 것을 확인할 수 있었다.

여기서 로그인을 하게되면 유저는 다시 원래의 사이트(깃헙 내앱 등록 시 설정해두었던 callback url)으로 돌아오게 되고(리디렉트), 어떤 일시적으로 사용할 수 있는 코드가 code 파라미터로 주어지게 된다.

App 컴포넌트

만약 autorization 코드가 정상적으로 주어지게 된다면
App.js에 있는 componentDidMount 함수에서 getAcessToken 함수로 autorizationCode를 보낸다.

 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)
    }
  }

위에서 호출한 getAcessToken 함수는 아래와 같이 구현하여, access token을 받아온다.

받아온 authorization code로 다시 OAuth App에 요청해서 access token을 받을 수 있다. access token은 보안 유지가 필요하기 때문에 클라이언트에서 직접 OAuth App에 요청을 하는 방법은 보안에 취약할 수 있다. authorization code를 서버로 보내주고 서버에서 access token 요청을 하는 것이 적절하다.
깃허브 공식 문서에 따르면 이 url로 POST 요청을 보내게 되면 access token을 받을 수 있다고 아래와 같이 설명하고 있다.

async getAccessToken(authorizationCode) { 
    
    let callbackURL = 'http://localhost:8080/callback';
    await axios.post(callbackURL, {
      authorizationCode
    }) 
  //body에 authorizationCode를 실어서 POST요청을 보낸다.
    .then(res => { 
      this.setState({
        isLogin: true,
        accessToken: res.data.accessToken
      });
    })
    .catch(err => {
      //console.log(err);
    })
  }

isLogin이 true가 되면 이제 App.js에서 Login 컴포너트 -> Mypage 컴포넌트가 된다.

Mypage 컴포넌트

이제 깃허브에 로그인한 유저에 대한 정보를 요청할건데 이게 바로 공식 문서에 나온 API이다. 클라이언트가 엑세스 토큰을 가지고 resource server에 resource 엑세스하려고 할 때 꼭!!! authorization 헤더에 토큰을 명시해줘야한다!!!

async getGitHubUserInfo() {
    const { accessToken } = this.props
    let response = await axios.get('https://api.github.com/user', {
      headers: {
        authorization: `token ${accessToken}`, // 토큰 명시 필수!!!
      }
    })

    const { name, login, html_url, public_repos } = response.data

    this.setState({
      name,
      login,
      html_url,
      public_repos
    })
  }


(로그인 성공시 렌더되는 Mypage 컴포넌트 화면)

profile
우당탕탕 개발일기📝🤖

0개의 댓글