Search
▪️

Working with REST APIs - The Practical Application

REST APIs & The Rest of the Course

Node + Express App Setup ⇒ No changes
Routing / Endpoints ⇒ No real changes, more Http methods
Handling Request & Responses ⇒ Parse + Send JSON Data, no Views
Request Validation ⇒ No changes
Database Communication ⇒ No changes
Files, Uploads, Downloads ⇒ No changes (only on client-side)
Sessions & Cookies ⇒ No Session & Cookie Usage
Authentication ⇒ Different Authentication Approach

Understanding the Frontend Setup

React.js는 Frontend JavaScript Framework이며, Browser에서 동작한다. React.js는 일반적인 HTML Page와는 달리 Single Page Application을 만들 수 있게 해준다.

Fetching Lists of Products

Backend Code가 8080에서 구동되고 있고, Frontend Code가 3000에서 구동되고 있다고 가정해보자. 이 두 Application들은 서로 다른 Server에 의해서 구동되고 있음을 알 수 있다.
이와 같은 모습은 흔히 볼수 있게 되는데, React.js같은 Frontend-Only Framework들은 Static Host로 구동된다. Static Host라 함은 HTML, CSS, JavaScript로 이뤄진 Application을 위해서 최적화된 것이다. 따라서 Backend와 Frontend를 같이 만들었다고 해도, 서로 다른 포트로 구동되게 되는 것이다.
서로 다른 포트로 구동되기 때문에 Domain이 서로 다름을 알 수 있고, 따라서 CORS Errors를 방지하기 위해 Header Setting이 필요하다.

Setting Up a Post Model

이전에 MongoDB를 이용하기 위해 ODM으로 Mongoose를 이용했었고, Mongoose Model을 정의하기 위해서 Schema Instance를 두어 Model을 생성할 수 있게 했었다.
Schema의 첫 번째 인자가 Schema에 대한 정의라고 한다면, 두번째 인자로 {timestamps: true}를 할당하면 createdAt과 updatedAt이 자동으로 기록되도록 설정된다.

Fetching a Single Post

then() catch() 에서는 비동기므로 Error에 대해서 next($err)만 쓰는 것으로 배웠는데, throw를 써도 됨
둘 차이는 다음과 같음. then()에서든 catch()에서든 next($err) 시에는 해당 catch()의 그 다음 catch()에 걸리게 된다. 반면에 then()에서 throw를 하게 되면 then()과 엮여 있는 catch()에서 걸리게 된다.

Uploading Images

REST API로 사진을 업로드할 때는, 기존 Server Code는 바뀌는 것 없이 Client에서의 Code가 살짝 바뀌게 된다.
기존 Server Code에서는 multipart/form-data를 통해서 File과 Plain Text에 대해서 모두 처리가 가능했다. 하지만, 새로 작성된 REST API를 이용하는 Client에서는 JSON을 이용하고, 이는 application/json으로 Type처리를 했었다. 이 때, JSON에 대한 처리는 Text 밖에 인식하지 못하므로 File 이용 시 application/json 을 이용하지 않고 Form Data로 처리해야 한다.
위와 같은 문제를 처리를 위해서 FormData()라는 Object가 필요하다. FormData는 Browser-Side의 JavaScript에서 Built-In으로 지원하는 Object이다. const formData = new FormData()와 같이 선언하여 사용하고, append() Method를 통해서 FormData에 추가할 데이터들을 인자로 넣는다. 첫 번째 인자는 Field Name, 두 번째 인자는 Field Value가 되겠다.
해당 Form Data는 Request Body에 담을 때, 별도의 JSON으로 Stringify가 필요하지 않다. 또한 application/json Header도 지워줘야 한다. 그렇지 않으면 정확하지 않게 Parsing하게 되어 Server가 Crash될 수 있다.

How Does Authentication Work?

REST API를 이용하게 되더라도 여전히 Client와 Server는 존재하고, Client는 Auth Data를 Server로 보내게 된다.
기존에는 받은 데이터가 Valid한지 Server에서 검증하는 절차를 거쳤고, Valid하다면 Session을 만들어 Client에게 Session ID 를 가진 쿠키를 할당했었다. 이 Session을 통해서 Server는 Client가 Authorized 된 것으로 간주했었다. 하지만 REST API는 Stateless이므로 Session을 사용하지 않는다. 따라서 Auth Data에 대한 검증 방법이 필요하다. (REST API를 이용하게 되면, Server와 Client는 철저하게 분리되어 이용되고 이에 따라 모든 Request들은 독립적이다. 즉, Client의 State에 대해서는 신경 쓰지 않아도 된다.)
REST API에서 모든 Request들이 처리 되기 위해선 Authenticate되어야 할 데이터들을 모두 갖고 있어야 하는 것이고, Authentication이 있어야 사용할 수 있는 데이터들은 Client가 갖고 있는 Token을 통해서 검증하게 된다. (Server에 Client들에 대한 State와 정보들만 없는 것 뿐이지 Validation은 진행한다. 특히 Token이 생성 될 때는, Authentication을 얻기 위해 Validation을 진행 후, Valid한 데이터를 가진 사용자라면 Response에 Token을 담아서 Client에게 보내게 된다. 이 발급된 Token을 통해서 Valid한 사용자임을 증명하는 것이고 이를 통해서 Server는 Stateless하게 유지할 수 있는 것이다.)
Token은 사용자의 정보들을 모두 갖고 있기 때문에 위조되지 않은 사용자임을 증명하는 수단이고, Server에서 생성된다. Server에서 발급된 Token을 Client에게 보내게 되면, Client는 해당 Token을 Storage에 저장하게 된다. 이렇게 저장한 Token은 매 Request마다 붙어서 Server로 보내지게 된다.
Token이 다른 Server에도 사용 가능한가? → Token의 유효성 범위는 Token을 생성한 Server까지만 이다.
임의로 생성한 Token이나 Frontend에서 Token의 위조, 변조를 시도한 후 Server로 보내게 되면, Server에서는 Token에 대한 위조 변조를 감지할 수 있게 된다. (Server에서는 특정 알고리즘으로 Token을 생성하게 되는데, 이 Token은 Private Key를 통해서 생성되기 때문에 위조, 변조가 불가능하다.)
Token안에는 그렇다면 어떤 내용들이 들어가 있는가? → JSON 데이터가 들어가 있다. 정확히 말하자면 JSON 데이터에 Signature가 함께 들어가 있다. JSON 데이터와 Signature가 합쳐진 것을 JSON Web Token (JWT) 라고 부른다. (Signature는 Private Key를 통해서 Server에서 생성된다. 즉, Signature의 유효성은 Server에 있는 Private Key를 통해서 검증된다. 따라서 위조, 변조가 된 Token은 Server에서 Private Key로 검증이 안 되니 위조, 변조에 대해서 감지할 수 있는 것이다.)

Starting with User Login

JWT를 이용하기 위해선 아래 명령어를 통해서 Third Party Package를 다운로드 받는다.
npm install —save jsonwebtoken
JWT에는 JSON 데이터와 Signature라는 Private Key가 있다고 했었는데, Signature를 생성하기 위해서는 const token = jwt.sign()을 사용해야 한다. 이렇게 생성한 Signature 안에 JSON 데이터를 넣는다. 즉, sign() Method의 인자로 JSON 데이터를 준다. (JSON 데이터로 들어가는 정보들은 email과 같은 사용자의 정보들인데, Password 값은 넣지 않도록 한다. 직접 값을 읽을 수 있든 없든 JWT는 Client 측에 저장되는 값들이기 때문이다.)
JSON 데이터를 sign() Method의 인자로 주었다면, 두 번째 인자는 String 값이다. 두 번째 인자는 Secret 값으로 JWT의 Private Key로 작용하게 된다. (따라서 해당 Key 값은 Server만 알며, Token의 검증도 Server만 할 수 있는 것이다.) 일반적으로 Secret 값은 긴 String 값으로 운영된다.
JWT는 Token이기 때문에, Expiry Time이 필요하다. 세 번째로 주어지는 인자가 바로 Expiry Time이다. Expiry Time은 Object로 주어지며 {expiresIn : '1h'}와 같이 주어진다. 이렇게 지정하면 1시간 뒤에는 Invalid한 Token이 된다.
이런 Token들은 Client에 저장되기 때문에 탈취될 수도 있다. 사용자가 Logout을 하지 않고, 다른 사용자가 이를 Browser Storage에서 Copy 해간다면 탈취되는 것이다. 이런 경우에는 사용자의 권한을 얻게 되지만 Token이 영원히 지속되는 것은 아니기 때문에 정해진 시간 이후에는 권한을 얻을 수 없게 된다. (어차피 Token을 갖고 있는 것만으로는 계정의 정보를 알아낼 수 없다.) 따라서 Token을 누가 가져간다고 해도, 쉽게 정보를 빼갈 수는 없다는 것이다.
이렇게 발급 받은 Token을 Login 외에 다른 기능들과도 연결 지어야 한다.
** jwt.io에 가면 더 자세한 정보들을 찾아볼 수 있다. (해당 사이트에서 Input에 따른 Token 값을 직접 눈으로 확인할 수 있다. 사이트에서 보면 알 수 있듯이, Input으로 쓰이는 값에 대해서 조작하면, Token값도 같이 바뀌는 것을 볼 수 있다. 따라서 Valid하지 않게 된다. 이것이 Token 값이 위조 및 변조가 되어도, Server에서 이에 대해서 감지할 수 있는 이유이다.)

Using & Validating the Token

Token을 검증하는 Middleware를 두면서 작업을 하게 된다. 이에 따라서 Token을 Request에 담아야 하는데, 담는 방법은 몇 가지가 존재한다.
1.
Query Parameter로 넘긴다. (좋은 방법은 아니지만 쓸 수는 있다.)
2.
Request의 Body에 담는다. (GET Method의 경우 Body가 없기 때문에 적합하진 않다.)
3.
Request의 Header에 담는다. (가장 좋은 방법이다. URL을 깔끔하게 유지할 수 있을 뿐더러 Header의 목적성에도 걸맞다.)
Header에 Token을 담을 때는, Authorization이라는 Header에 담는다. (Header에는 원하는 Custom Header를 담을 수 있지만, Authorization은 Official Header이다.) Front(React)에서는 아래와 같이 설정하여 Token을 받는다.
{ headers : { Authorization : 'Bearer ' + this.props.token } }
Bearer를 쓰는 이유는 일종의 Convention으로 Token의 Type을 명시하는 것이다. (없어도 무방하지만 Convention이다.)
위와 같이 특정 기능에서 Request를 보내는 Authorization Header에 Token을 담았다면, 이를 Middleware에서 검증하는 작업을 위해서 Token을 Header에서 가져오는 작업 및 Decode의 작업이 필요하다.
Token을 Header에서 가져올 때는 Authorization Header에서 get() Method를 통해서 읽어오며, Bearer라는 Convention 때문에 White Place에 따라서 Split을 해줘야 한다.
Decoding의 경우 jsonwebtoken Third Party Package의 도움을 받아서 Decoding한다. (Decoding 시도를 할 때는 Try - Catch를 꼭 이용하도록 한다.) verify()라는 Method를 통해서, 받은 Token이 유효한 Token인지 검증할 수 있는 것이다. (decode()라는 Method가 있지만, 이는 말 그대로 Decode만 진행할 뿐이고 Verify에 대해선 수행하지 않는다.)

Clearing Post-User Relations

List에서 항목을 삭제할 때는 pull() Method를 찾아서 이용 가능 여부를 확인하고 이용하는 것도 좋다.
** Method가 GET이 아니라면 무조건 명시해야 하고, Authorization이 필요하다면 header에 명시해야 한다. 또한 JSON 타입이라면 header에 Content-Type 또한 명시해야 하며, Body에 JSON 타입으로 보낼 내용을 명시하는 것도 잊지 말아야 한다.