인증/보안 - OAuth 인증 구현

2021. 11. 26. 16:11·SE Bootcamp 내용 정리

OAuth 인증 구현하기

학습 목표

* OAuth의 작동 방식의 이해

* OAuth로 로그인 가능한 애플리케이션을 제작

* Authentication과 Authorization의 차이를 이해

* "브라우저" - "내 서버" - "인증 대행 서비스"간 요청/응답을 주고받는 다이어그램을 그려보기

* OAuth의 중요 키워드

getting strated

토큰 인증 방식이 주된 인증 방식으로 사용되는 가운데 OAuth 인증 방식이 등장했고, 점차 영향력을 넓혀가기 시작했습니다.
또한 이런 OAuth 인증 방식을 도입해 달라는 의견이 있어 이를 도입하는 프로젝트에 담당자로 배정되었습니다.
이제 여러분은 OAuth 인증 방식을 구현해야 합니다.

소셜 로그인 버튼을 눌렀을 때, mypage에서 나의 정보를 확인할 수 있도록 구현
-> 여기서는 github 와 연동한 로그인 및 유저 정보를 확인할 수 있도록(mypage) 구현해 보자

GitHub에 내 앱 등록

사전 작업으로, 만들 애플리케이션을 gitHub에 OAuth 앱으로 등록해야 한다

 

등록 방법 및 절차는 아래 링크를 참조
https://www.oauth.com/oauth2-servers/accessing-data/create-an-application/

 

Create an Application - OAuth 2.0 Simplified

Before we can begin, we'll need to create an application on GitHub in order to get a client ID and client secret. On GitHub.com, from the "Settings" page,

www.oauth.com

Homepage URL 및 Authorization callback URL 부분을 유의깊게 보고 설정해야 한다

  • Authorization callback URL?
    OAuth 메커니즘 자체가, 인증 과정이 끝난 후 리디렉션을 통해 다시 내 앱으로 이동하는 원리이므로, 내 앱으로 돌아가기 위한 Authorization callback URL이 필요한 것

환경 설정

.env 파일에 환경 변수 설정을 하자(GitHub App에서 제공하는 Client ID 및 Client Secret 정보를 변수에 담기)
-> Client Secret은 항상 비밀로 지켜져야 하므로, gitignore 에 등록해 두자(.env)
-> Client ID와 Secret 정보는 GitHub 설정의 등록한 OAuth 앱 설정 부분에서 찾을 수 있다

서버 부분

express를 이용해 만든 간단한 웹 서버로, Access token을 발급받는 과정은 서버에서 이루어지는 것이 더욱 안전하기 때문에 클라이언트에서 받아온 Authorization code를 서버의 /callback 엔드포인트로 전달해 서버에서 github App에게 Access token 발급을 요청하는 구조로 만들어야 한다

./controller/callback

인증 정보를 바탕으로 Access token을 받아올 수 있도록 도와주는 라우터 부분이다
-> authorization code를 이용해 access token을 발급받기 위해 authorization server인 gitHub로 post 요청을 보내야 한다

 

gitHub는 이와 관련하여 API를 제공하고 있다

 

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

 

Identifying and authorizing users for GitHub Apps - GitHub Docs

When your GitHub App acts on behalf of a user, it performs user-to-server requests. These requests must be authorized with a user's access token. User-to-server requests include requesting data for a user, like determining which repositories to display to

docs.github.com

module.exports = (req, res) => {
  // req의 body로 authorization code가 들어옵니다. console.log를 통해 서버의 터미널창에서 확인해보세요
  // console.log(req.body);

  // TODO : 이제 authorization code를 이용해 access token을 발급받기 위한 post 요청을 보냅니다

  //axios 를 이용하여 요청 처리
  axios
  .post('https://github.com/login/oauth/access_token',{
    client_id: clientID,
    client_secret: clientSecret,
    code: req.body.authorizationCode,
  }, {
    // config 부분
    headers:{ // !accept 부분을 headers 안에 담아 줘야 한다!
      accept: 'application/json',
    },
    // withCredentials: true,  //이 부분은 header에 값을 담는 것이 아니므로 굳이 필요 없음
  })
  .then((result) => {
    // console.log(result.data)    // 받아온 result의 data 부분을 확인해 보면 access token이 담긴 것을 알 수 있음
    let aToken = result.data.access_token
   // 적절한 응답(res) 메시지 작성
  })
  .catch(e => res.status(404))
}

./controller/images

받아온 Access token을 확인한 후, local(서버 기준 로컬임)에 저장된 resource images를 클라이언트로 보내주는 라우터 부분이다

 

기본 로직은 Mypage로부터 access token을 제대로 받아온 것이 맞다면, resource server의 images를 클라이언트로 보내 주고, 제대로 받아 온것이 아니라면(주어지지 않는다면) 접근 권한을 제한해도록 구현해야 한다

module.exports = (req, res) => {
    // req의 headers 부분을 확인 해 보자
  // console.log(req.headers.authorization);
  //access token이 주어지지 않는 경우 접근 권한을 제한해야 한다
  if(!req.headers.authorization){ // 주어지지 않는 경우 -> authorization code를 발급받지 못한 경우
    // 적절한 응답 메시지 작성
    return;
  }
  //access token이 주어지는 경우 접근 권한을 부여
  else{ // uthorization code를 발급받은 경우
    // 응답(res)에 image를 담아서 보내도록 작성  // {images: images} 이므로  key-value 이름 같으므로 축약 가능
  }
}

클라이언트 부분

Login Component:

가장 먼저, gitHub(Authorization server)에 요청을 보내 Authorization code를 받아와야 한다

 

gitHub로부터 사용자 인증을 위해 적절한 gitHub 인증 URL로 이동하도록 코드를 작성해야 한다
OAuth 인증이 완료되면 gitHub는 authorization code와 함께 callback url로 리디렉션해 준다

 

작성 방법은 gitHub 공식 문서를 참고하자


https://docs.github.com/en/free-pro-team@latest/developers/apps/identifying-and-authorizing-users-for-github-apps

 

Identifying and authorizing users for GitHub Apps - GitHub Docs

When your GitHub App acts on behalf of a user, it performs user-to-server requests. These requests must be authorized with a user's access token. User-to-server requests include requesting data for a user, like determining which repositories to display to

docs.github.com

 // 공식 문서의 옵션으로 사용가능한 파라미터 값을 확인 해보자
 // client_id가 필수임을 확인할 수 있으므로, 이를 이용해 보자
 // 파라미터이므로 `?`연산자를 이용

this.GITHUB_LOGIN_URL = 'https://github.com/login/oauth/authorize?client_id=내아이디값'
  }

App Component

Authorization code를 받아왔다면 해당 코드를 server(server > index.js)에 전달해 Access token을 받아올 수 있다
받아온 Access token은 App 컴포넌트의 state에 저장한 후, Mypage 컴포넌트에서 props로 내려받아 활용하도록 로직을 작성하면 된다


async getAccessToken(authorizationCode) { //! async라는 것은 비동기 선언

    // access token은 보안 유지가 필요하기 때문에 클라이언트에서 직접 OAuth App에 요청을 하는 방법은 보안에 취약할 수 있습니다.
    // authorization code를 서버로 보내주고 서버에서 access token 요청을 하는 것이 적절합니다.

    // TODO: 서버의 /callback 엔드포인트로 authorization code를 보내주고 access token을 받아옵니다.
    // access token을 받아온 후
    //  - 로그인 상태를 true로 변경하고,
    //  - state에 access token을 저장하세요

    await axios   //! 이 비동기 통신을 동기적으로 수행하고 싶으니 앞에 await 를 붙인다.
    .post('http://localhost:8080/callback', {    
      // 전달할 데이터 부분
      authorizationCode
    },
  // 헤더에 값을 담는게 아니므로 header(config) 부분은 생략해도 됨
    )
    .then((result) => {
      this.setState({isLogin의 상태 변경, accessToken의 상태 변경})
    })
  }


  ...

  // 작성되어 있는 부분
  componentDidMount() {
    const url = new URL(window.location.href)
    const authorizationCode = url.searchParams.get('code')  //! url에 있는 파라미터 code의 값을 가져옴
    // console.log(authorizationCode)
    if (authorizationCode) {
      // authorization server로부터 클라이언트로 리디렉션된 경우, authorization code가 함께 전달됨
      // ex) http://localhost:3000/?code=5e52fb85d6a1ed46a51f
      // console.log("마운트 타고있는지 테스트?")
      this.getAccessToken(authorizationCode)
    }
  }

Mypage Component

받아온 Access Token으로 서버에 리소스에 대한 API 요청을 해야 하는 부분이다
Access Token을 전달하는 방식은 앞서 토큰에서 배웠던 Bearer Token 을 headers에 담아 주어 전달할 수 있다

 

Mypage에서 받아와야 할 리소스는 2가지

    1. 먼저 Github API 요청에 Access token을 함께 보내어 유저 정보를 받아와야 함
    1. local server에 저장된 이미지들을 받아와야 함

완성된 마이페이지 화면에는 Github user 정보와 Resource 서버에 있는 이미지들이 출력되어야 한다

 

getGitHubUserInfo 메서드와 getImages 메서드를 완성해 줘야 한다
-> gitHub API 공식 문서를 참고해서 작성해보자

https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user

 

Users - GitHub Docs

Many of the resources on the users API provide a shortcut for getting information about the currently authenticated user. If a request URL does not include a {username} parameter then the response will be for the logged in user (and you must pass authentic

docs.github.com

// 현재 공식 문서에는 octokit 모듈을 설치하여 진행하도록 되어 있으나, 기존 axios 요청을 이용해도 된다

// octokit 모듈 설치 후 사용 시
// async getGitHubUserInfo() {

  //   // Octokit 객체를 생성
  //   const octokit = new Octokit({ auth: this.props.accessToken });
  //   await octokit.request('GET /user').then((res) => {
  //     this.setState(res.data)
  //   })
  // }



   // axios 이용
  async getGitHubUserInfo() {
    // console.log(accessToken)    // 확인해 보면 token #$@@$%# 이런식으로 들어옴을 알 수 있다
    await axios.get('https://api.github.com/user', {
      headers: {
        authorization: `token ${this.props.accessToken}`,
      },
      // withCredentials: true 해당 설정을 활성화 했을 때, console에 cors 에러창이 뜬다는 점
      // 앱 작동 상에는 문제가 없으나, 추후 확인이 필요할 듯 하다?
    })
    .then((res) => {
      const { name, login, html_url, public_repos } = res.data
      this.setState({
       // 적절한 상태 값으로 변경
      })
    }) 
  }

...


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 상태 값})
    })
  }


...

// return 부분 내에서

<div id="images">
    {/* TODO: 여기에 img 태그를 이용해 resource server로 부터 받은 이미지를 출력하세요 */}
     {/* this.state.images.map(image => 
              <img 태그 써서 적절하게 출력되도록 하자/>
            ) */}
저작자표시

'SE Bootcamp 내용 정리' 카테고리의 다른 글

컴퓨터 공학 - 핵심 내용 정리  (0) 2021.11.26
컴퓨터 공학 - 기초  (0) 2021.11.26
인증/보안 - 기초 2: CSRF, Token  (0) 2021.11.25
인증/보안 - session 기반 인증 구현하기 실습  (0) 2021.11.25
인증/보안 - 기초 1: HTTPS, Hashing, Cookie, Session  (0) 2021.11.24
'SE Bootcamp 내용 정리' 카테고리의 다른 글
  • 컴퓨터 공학 - 핵심 내용 정리
  • 컴퓨터 공학 - 기초
  • 인증/보안 - 기초 2: CSRF, Token
  • 인증/보안 - session 기반 인증 구현하기 실습
레실이
레실이
  • 레실이
    레실이의 티스토리
    레실이
  • 전체
    오늘
    어제
    • 분류 전체보기 (81)
      • SE Bootcamp 내용 정리 (63)
      • 알고리즘 연습 (7)
      • Project 주저리 (4)
      • 기술 면접 source (3)
      • 개발 일상 (2)
      • 생성 AI 활용 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    useState
    CORS
    데이터베이스
    DOM
    node
    CSR
    react 기초
    node.js
    PickAndDrink
    CSS
    cmarket
    JS
    Ajax
    linux 기본 명령어
    중복 순열
    문자열
    state
    Linux
    promise
    IT
    자료구조
    JavaScript
    인증/보안
    ORM
    알고리즘
    객체
    react
    MVC
    useRef
    KPT
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
레실이
인증/보안 - OAuth 인증 구현
상단으로

티스토리툴바