09/12, saturday self study

sonofhuman20·2020년 9월 12일
1

Today I Learned

목록 보기
25/40
post-thumbnail

오늘 할 일

오늘은 어제 공부했던 OAuth 를 공식문서와 어느 정도 검증된 곳들에서 설명해 준 것들을 다시 한 번 살펴보고, sequelize sprint 에서 막혔던 부분도 다시 한 번 공부할 예정이다. 또, JWT(JSON Web Token) 도 살펴보라는 이야기를 많이 해서(실무에서 정말 많이 쓴다고 한다) 겸사겸사 살펴보려고 한다. 해야 할 공부가 정말 많다!


OAuth-Servers Document

Getting Ready - OAuth 2.0 Simplified

이번에도 공식문서를 보고 정리하는 식으로 공부를 했다. 구글링을 해 보니 naver D2 에서도 정리를 해 놨던데, 코드스테이츠 측에서 일단은 Getting Ready, Accessing Data in an OAuth Server 두 문단을 공식문서를 보는 걸 추천해서 공식문서의 두 단락을 먼저 보고 나서 나머지 글들을 한 번 확인해 볼 생각이다.

1. Getting Ready

시작하면서

웹앱을 만들든, 모바일앱을 만들든 간에 OAuth 의 사용을 시작할 때 염두해야 할 사항들에 대해 적은 단락이다.

Creating An Application

애플리케이션을 만들 때 일반적으로 계정을 만든다. 그리고 사용자는 계정 생성(회원가입) 을 통해 웹사이트의 로고, 웹사이트의 이름 등등 간단한 것들에 접근할 수 있는 권한을 부여받는다 (authorization) 일반적으로는 그렇게 회원가입 과정을 거치면 client_id 라는 걸 부여받는다. 이를 통해 사용자는 서비스와 소통한다(interact)

이 과정에서 중요한 점은 redirection URL 을 만들라는 것이다. OAuth 는 유저가 해당 애플리케이션에서 authorization 을 마친 뒤 redirection URL 으로 redirect 시키는데, 이 과정은 굉장히 중요하다. 여기서 자칫하면 유저의 정보를 탈취하려는 또 다른 애플리케이션들이 만들어지기 쉽다.

Redirect URLs and State

OAuth 2.0 API 는 그저 지정한 redirect URL 로 유저를 redirect 시킬 뿐이다. authorization code 나 token 등이 탈취되는 과정을 막기 위해서이다. 이런 redirection URL 을 만들 때 유의할 점들이 있다.

일단은 다양한 redirect URL 을 준비하면 좋다. 하나의 client_id 를 여러 개의 viewport 에서 동시에 사용하는 상황을 handling 할 수 있기 때문이다. 그래서 몇몇 서비스는 해당 상황을 감안해 다양한 redirect URL 을 만들 수 있게 한다.

또 이런 redirect URL 을 만들 때의 protocol 방식은 https 가 되어야 한다. 그렇게 해야 authorization 과정에서 정보가 도중에 탈취당하는 (intercept) 상황 등을 방지할 수 있다.

그리고 redirection URL 에 query string 을 노출시키는 상황은 최대한 피해라. https://example.com/auth?destination=account 이렇게 노출시키지 말고 그냥 endpoint 정도만 바꿔주어라. https://example.com/auth 정도로만.

위에 적은 여러 viewport 에서의 동시접속 handling 뿐만이 아니라, 다양한 곳에 가입경로를 포함시키는 경우때문에 여러 개의 redirection URL 이 필요한 경우도 있을 것이다. 예를 들자면 홈페이지의 로그인 화면에도 authentication button 을 구현해놓고, 어떤 public item 을 볼 때도 authentication button 을 구현하는 상황 등등. 이런 경우는 "다양한 곳에서 request 를 받아야 하니, 다양한 redirection URL 이 필요하겠네" 할 수도 있겠으나, OAuth에서는 위같은 경우를 위해 state parameter 를 제공한다.

2-1. Create an Application

시작하기 전에 github 에 가서 어플리케이션을 만들어주어야 한다. 앞으로 사용할 client idclietn secret 을 받아와야 하기 때문이다. 해당 과정은 github 의 developer setting 에서 진행할 수 있다.

이런 화면을 볼 수 있다. 우리가 아까 말했던 항목들이 보인다. authorization 을 완료한 use 를 token 과 같은 정보와 함께 해당 endpoint 로 redirect 를 시켜주는 데 사용하는 authorization callback URL 이라든지, 우리의 애플리케이션의 메인 주소로 사용할 homepage URL 이라든지. 저 부분을 우리가 만들어서 채워준다.

중요한 점은 client secret 은 정말정말 중요하게 취급해야 한다는 점이다. client id 의 경우는 사실상 공개정보로 취급된다. 왜냐면 해당정보는 authorization URL 을 만드는 데도 필요하고, 웹페이지의 JS source code 에 들어가야 할 수도 있기 때문이다.

그러나 client secret 은 정말 중요하게 취급해야 한다. 이를 절대 JS 파일 내부에 직접 작성하거나, git 에 올리는 일이 없도록 하자.

2-2. Setting up the environment

  • 해당 과정부터는 PHP 코드로 작성되어서, 공식 문서를 통해서는 과정만 검색하고 JS 로 구현한 OAuth - Github 인증 과정을 찾아보려고 한다.
  1. 일단은 서버를 켜고, 어떤 port number 를 사용할지 명시해준다
  2. Github API 에서 요구하는 AcceptUser-agent 라는 header 를 세팅한다.
  3. 그렇게 API 대로 요청해서 받은 response 를 JSON 으로 파싱한다.
  4. 만약 우리가 이 과정으로 access token 을 session 을 통해 받았다면, 이전의 OAuth header 에 그렇게 받은 access token 을 같이 담아서 authenticated request 를 보낸다.
  5. 그리고 그 외의 필요한 변수들을 설정한다.

2-3. Authorization Request

  • 마찬가지로 해당 과정도 PHP 로 작성되어서, 공식문서를 통해서는 과정만 검색하고 JS 로 구현한 또 다른 예제를 찾아보려고 한다.

이전 단락에서 2-2 의 과정을 통해 기본적인 설정을 다 완료하였으니, 이번 단락에서는 그 설정을 기반으로 OAuth 를 사용하는 과정을 설명한다.

  1. 우리가 맨 처음 페이지를 방문할 때 사용하는 query string 은 ?action=login 이다.
  2. 여기서 처음에 설명했던 state 라는 parameter 가 생성된다. client 를 보호하기 위해 사용한다. 이는 client 가 생성하는 임의의 문자열이고, session 안에 저장된다.
    1. 우리는 해당 parameter 를 추가적인 보안 검증 과정에 사용하고, github 는 authentication 과정을 거친 user 를 아까 우리가 설정한 redirection URL 로 보낼 때 state 를 query string 에 담는다.
  3. 이 부분에서 user 는 Github 의 OAuth authorization prompt 창을 보게 된다.
  4. user 가 해당 요청을 받아들이면 (approves), github 는 user 를 2-3-2-1 에서 말한 과정처럼 우리들의 redirection URL 에 codestate parameter 를 request 에 담아 넘겨준다.
  5. 이제 우리는 authorization 을 통해 받은 code 라는 요소를 access-token 과 맞교환 할 것이다(exchange)

2-4. Obtaining an Access Token

  • 이번 과정도 마찬가지로 위와 마찬가지이다. PHP 로 작성된 코드이다.

2-3 의 과정에서 authentication 을 거친 user 가 다시 우리의 애플리케이션으로 돌아왔다면, 이제 codestate 라는 parameter 를 query string 으로부터 가져올 수 있게 된다.

state parameter 는 우리가 초기에 부여해주었던 것과 똑같다, 우리는 애플리케이션에서 그 state parameter 가 초기값과 같은지 검사를 함으로써 혹시라도 우리의 정보를 탈취하려는 시도를 막아낼 수 있다. 다시 말 해, CSRF attack 을 방어하는 과정이라는 이야기이다.

그렇게 state 의 동일함을 확인한 뒤에는 github 의 token endpoint 로 request 요청을 보내 authorization code 를 access token 과 교환한다. 해당 request 에는 우리들의 public 한 정보인 client ID 와, 우리가 소중히 관리해야 한다던 client secret 이 담겨있다. redirect URL 도 이전에 authorization code 와 관련하여 사용했던 것과 똑같은 URL 을 발송한다.

이 모든 요청이 정상적으로 진행되었으면, 이제 github 는 access token 을 발급하고 response 에 담아 전달한다. 우리는 해당 token 을 session 에 담고, 홈페이지로 redirect 를 걸어주고, 유저는 로그인을 한다.

{
  "access_token": "e2f8c8e136c73b1e909bb1021b3b4c29",
  "token_type": "Bearer",
  "scope": "public_repo,user"
}

우리가 github 로 부터 받는 response 는 이런 식이다.

우리는 이 access_token 을 받아 우리의 session 에 저장한다. 이 다음에 해당 페이지를 방문할 때도, 페이지는 이미 우리가 앞의 과정들을 통해 발급받은 access-token 을 통해 user 를 판별하고, 우리가 이전에 만들었던 로그인을 했을 때 나오는 화면들을 띄워준다.

OAuth 의 예시 코드에서 error handling 을 한 부분은 없었다. 실제로 우리가 서비스를 기획할 때에는 github 로 부터 error 가 발생한 경우, 사용자에게 적절한 메시지를 띄워주는 error handling 과정도 포함해야 한다.


Making OAuth with github by JS from scratch

이제 한 번 OAuth 를 통한 github 인증 과정을 JS 로 구현해보려고 한다. accessing data in an OAuth Server 단락을 쭉 참고하고, PHP 로 작성된 코드를 최대한 JS 로 구현해볼 예정이다.

0. SPEC

일단은 기본적으론 express 를 기반으로 서버를 구성하였으며, request 로깅을 위한 미들웨어로 morgan 을 사용하였다. session 객체 핸들링은 express-session 을, 그리고 외부에서 로컬로 접속할 수 있도록 하기 위해서 ngrok 을 사용하였다.

1. github dev setting 에서 application 등록하기

일단은 외부에서 접속할 수 있도록 ngrok 을 통해 도메인을 만들어주었다. 나는 8080 포트를 사용하고 있기에, ngrok http 8080 을 통해 실행해주었다. 또, session 관련 처리는 express-session 을 이용하였다.

추가로, ngrok 의 설치는 현재 macOS 를 사용하는 관계로 brew cask install ngrok 명령어를 통해 진행하였다.

ngrok by @inconshreveable                                       (Ctrl+C to quit)
                                                                                
Session Status                online                                            
Session Expires               7 hours, 59 minutes                               
Version                       2.3.35                                            
Region                        United States (us)                                
Web Interface                 http://127.0.0.1:4040                             
Forwarding                    http://c8bcdd82693b.ngrok.io -> http://localhost:8
Forwarding                    https://c8bcdd82693b.ngrok.io -> http://localhost:
                                                                                
Connections                   ttl     opn     rt1     rt5     p50     p90       
                              0       0       0.00    0.00    0.00    0.00

이런 창이 나오는 걸 확인할 수 있었다. http 와 https 의 도메인이 존재하는데, 홈페이지 도메인으로는 http 를, 그리고 redirection 도메인으로는 https 를 사용하였다. 그 이유는 OAuth 의 문서에서 말 한 것처럼 redirection 용 링크는 https 를 사용하여 정보를 탈취할 수 있는 가능성을 조기차단하는 것이 좋다고 했기 때문이다.

공식문서에서 나온대로 설정을 해 주었고, 보이진 않지만 위에 있는 client ID 와 client SECRET 또한 발급을 잘 받았다.

이렇게 postman 을 통해 보낸 GET request 도 잘 날아가고 있는 걸 확인할 수 있다.

2. github API 를 참고해서 인증해보기

OAuth Authorizations

Deprecation Notice: GitHub will discontinue the OAuth Authorizations API, which is used by integrations to create personal access tokens and OAuth tokens, and you must now create these tokens using our web application flow. The OAuth Authorizations API will be removed on November, 13, 2020. For more information, including scheduled brownouts, see the blog post.

해당 문서를 보고있는데, OAuth authorizations API 가 조만간 사라지고(2020/10/13 부로), github 에서 제공하는 새로운 web application flow 를 통하여 token 을 발급하라는 이야기가 있었다. 그래서 해당 문서에 나와있는 API를 참조하기로 했다.

Authorizing OAuth Apps

GET https://github.com/login/oauth/authorize

어떻게 보내지?

일단 이 API 를 보면서 가장 먼저 든 생각은, "어떻게 request 를 할까" 였다. 물론 기본적으로 지원하는 fetch 가 있지만, axios 라는 라이브러리를 통해서 request 를 하는 경우도 꽤 보았기 때문이다. 그래서 "axios vs fetch" 라는 키워드로 검색하니, 나랑 비슷한 고민을 하는 사람들이 많았고, 그중에서 그 고민에 대한 나름의 간단한 답을 찾을 수 있을 법한 글을 찾았다.

[React / React Naive TIPS] axios 와 fetch 어떤 것을 사용할까?

18년도 글이긴 한데, 일단 fetch API 가 크게 바뀌었을 거 같진 않다는 생각이 들었다. 그리고 axios 문서를 들어가 보았는데 fetch 보다 조금 더 간결해 보였다. 이 글에서도 "axios 가 확실히 조금 더 편하게 사용할 수 있게 디자인되었다" 라는 이야기를 했는데, 그 이야기가 생각나며 약간 공감이 갔다.

사실 모든 걸 차치하고서라도 axios 를 한 번도 사용해 본 적이 없기 때문에, axios 를 사용해 보자는 생각에 axios 를 사용해 보기로 했다. npm 옆의 weekly download 그래프도 도 쭉 올라가고 있으니 대세에 편승한다는 느낌으로다가, npm install axios --save 를 통해 설치해주었다.

axios 로 github API 에 요청하기

const axios = require('axios');

let authRequest = () => {
    axios.get("https://github.com/login/oauth/authorize", {
        params: {
            client_id: "535d14a5308cacbed013",
            state: "pravda"
        }
    }).then(
        (res) => console.log(res)
    )
}

이런 식으로 요청을 보내보았다. 200 코드가 잘 떴고, 무수한(...) 정보들이 쏟아져 나왔다. 그렇게 해서 건진 query string 의 code 를 express 의 query 를 통해서 res.query.code 로 접근하였다. 그 다음에는 그 값과 내 client_id, client_secret 을 통해 [https://github.com/login/oauth/access_token](https://github.com/login/oauth/access_token) 에 post 요청을 보내주었고, 그렇게 해서 받은 response 를 res.data 를 통해 접근하여 내 access-token 을 알아낼 수 있었다.

app.get('/callback', async (req, res) => {
    let userCode = await req.query.code
    axios.post('https://github.com/login/oauth/access_token', {
        client_id: "535d14a5308cacbed013",
        client_secret: process.env.PASSWORD,
        code: String(userCode)
    }).then(
        (res) => console.log(res.data)
    )
})

해당 코드를 통해 github API 로 body 에 필요한 정보들을 담아 POST request 를 발송하였고

GET / 304 - - 3.377 ms
GET / 304 - - 1.322 ms
access_token=mytoken...&scope=&token_type=bearer

그렇게 token 을 얻을 수 있었다. 이제 이렇게 발행한 token 으로 다른 API들을 사용하면 된다!


토큰 받아오는 거 밑바닥부터 구현하는 데만 이렇게 걸려버렸다 ㅠㅠ DB sprint 재도전과 JWT 는 내일 좀 쉬다가, 마저 정리해야겠다.


Nested Concepts

CSRF attack

Cross-site request forgery

Cross-Site Request Forgery, 사이트 간 요청 위조 공격을 의미하며, 유저가 의도하지 않은 행동을 요청을 위조하여 실행하는 과정이다.

cookie 나 session 은 분명히 유저가 정상적으로 취득한 것이 맞는데, 해당 쿠키를 지닌 상태로 유저가 호기심을 가지게끔 이상한 URL 등을 보내, 그 쿠키를 기반으로 유저가 실행하려 하지 않았던 일들을 한다는 게 중요하다.

profile
правда и красота, truth and beauty

0개의 댓글