Dev/SW Engineering

38. Authentication - Express cookie-parser

HJChung 2020. 9. 13. 21:32

1. Express의 cookie-parser middleware의 사용

expressjs.com/en/resources/middleware/cookie-parser.html

 

Express cookie-parser middleware

cookie-parser Parse Cookie header and populate req.cookies with an object keyed by the cookie names. Optionally you may enable signed cookie support by passing a secret string, which assigns req.secret so it may be used by other middleware. Installation $

expressjs.com

cookie-parser는 Request의 headers에 있는 cookie 정보를 해석해주는 미들웨어이다. 

아래를 보면 req.headers.cookie에 있는 cookie 정보 형태와 req.cookies를 사용하여 안에 들어있는 cookie 정보 형태의 차이를 알 수 있다. 

이번 sprint에서는 Authentication에 대해 배우는 만큼 express의 cookie-parser를 사용하여 회원가입, 로그인, 로그아웃과 같은 기능을 구현해보는 과정이었다. 

2. 파일구조 및 동작 flow 확인하기

3. 각 라우팅에 해당하는 controller 기능 구현하기

1) GET /요청이 오면 status code 200, res.text 는 'Success'

app.get('/', (req, res) => {
  res.status(200).send('Success')
})

2) POST /user/signup 기능 구현

: 유저가 회원가입을 할 때 회원 정보를 DB에 업뎃

유저가 회원가입을 할 때는 어떤 경우의 수가 있을까?

  • 1. 회원가입이 안되어있는 경우 => DB에 없음 => 그러면 회원가입을 하게하고 => DB에 추가(statusCode: 200)
  • 2. 회원가입이 되어 있는 경우 => DB에 있음 => 이미 회원가입이 되어 있다고 한다 (statusCode: 409, res.text: 'Already exists user')

=> 이 두가지 경우를 모두 표현할 수 있는 DB 접근 메서드는?(즉, sequelize 메서드는?) findOrCreate

The method findOrCreate will create an entry in the table unless it can find one fulfilling the query options.

(공식 홈페이지:  sequelize.org/master/manual/model-querying-finders.html#-code-findorcreate--code- , sequelize.org/master/class/lib/model.js~Model.html#static-method-findOrCreate)

 

저저번주 sprint 때도 정말 뼈저리게 느낀 거지만, "새로운 메서드를 사용할 때, 공식문서에 가서 해당 메서드의 parameter와 return 값을 잘 보아야한다. 

※ return 값의 boolean은 'The boolean indicating whether this instance was just created'을 의미한다. 

즉, 이미 DB에 있어서 find하면! just created되지 않았기 때문에  boolean 값으로  false가 반환되고, DB에 없어서 그 instance를 just created 하였다면 boolean값이 true가 된다. 

const { users } = require('../../models')

module.exports = {
  post: (req, res) => {
    //TODO: 유저가 회원가입을 했을 때,
    //회원정보를 데이터베이스에 저장하도록 구현하세요.
    //console.log(req.body) // req.body에 내용이 있을 것 같아서 찍어봤던니, { email: 'test@gmail.com', username: 'testuser',password: 'asdfasdf' }라고 잘 나온다.
    const { email, username, password } = req.body
    users
      .findOrCreate({
        where: { email: email },
        defaults: { email: email, username: username, password: password },
      })
      .then(([result, justCreated]) => {
        if (!justCreated) {
          //justCreated가 false면 이미 있어서 안 만들었다는 것(justCreated 안했다는 것)
          res.status(409).send('Already exists user')
        } else {
          res.status(200).json(result)
        }
      })
  },
}

3) POST /user/signin 기능 구현

: 유저가 로그인을 했을 때, 회원정보를 DB에서 확인하고 회원의 id를 session에 담아두도록 한다. 

libertegrace.tistory.com/entry/37-Authentication-Session에서 session을 복습하면서 session과 cookie의 가장 큰 차이점은 session의 사용, 즉

  1. sessionID(유일한 ID; token)을 만들어서 
  2. sessionID을 속성명으로 하고 주요 정보를 속성값으로 하여 세션 객체(SESSION)에 저장한다. 
  3. 그리고 sessionID(유일한 ID; token)을 클라이언트에 쿠키로 보낸다. (Set-Cookie 사용해야겠지?!)

였다.

=> 여기선 유일한 ID를 users 테이블 해당 회원 정보의 'id'로 해주라고 한 것이고, 이를 위와 동일한 순서로 session에 담아주면 된다. 

 

유저가 로그인을 할 때는 어떤 경우의 수가 있을까?

  • 1. 회원가입 되어있는 경우 => DB에서 해당 회원정보 확인해서 => 회원 정보중 id(유일한 ID; token)을 클라이언트에 쿠키로 보낸다.(이게 바로 session에 담아둔다는 의미) (Set-Cookie 사용할 수도 있지만 CookieParser middleware을 써보자!)
  • 2. 회원가입이 안 되있는 회원일 경우 => DB에서 해당 회원정보 확인해보니 => 없다. (statusCode: 404, res.text: 'unvalid user')

=> 이 두가지 경우를 모두 표현할 수 있는 DB 접근 메서드는?(즉, sequelize 메서드는?) findOne

The method findOrCreate will search for a single instance. Returns the first instance found, or null if none can be found.

(공식 홈페이지: sequelize.org/master/manual/model-querying-finders.html#-code-findone--code-, sequelize.org/master/class/lib/model.js~Model.html#static-method-findOne)

주의! Model, boolean이 아니라 Model (or) null이다 

※ 주의! Model, boolean이 아니라 Model |(or) null이다 

즉, 위의 findOrCreate와 달리 

1. 회원가입 되어있는 경우 => return 값은 Promise <Model>

2. 회원가입이 안 되있는 회원일 경우 => return 값은 Promise<null>

 

 

 

 

 

 

 

 

 

① Set-Cookie 로 했을 때 

const { users } = require('../../models')

module.exports = {
  post: (req, res) => {
    // TODO : 유저가 로그인을 했을 때, 회원정보를 데이터베이스에서 확인하고,
    const { email, password } = req.body
    users.findOne({ where: { email: email } }).then((result) => {
      if (result) {
        //회원가입 되어있는 경우 => return 값은 Promise <Model>
       	res.setHeader('Set-Cookie', `session = ${result.id}`)
        res.status(200).json({id: `${result.id}`})
      } else {
        //회원가입이 안 되있는 회원일 경우 => return 값은 Promise<null>
        res.status(404).send('unvalid user')
      }
    })
  },
}

② cookie-parser 사용했을 때 

sailsjs.com/documentation/reference/response-res/res-cookie

const { users } = require('../../models')

module.exports = {
  post: (req, res) => {
    // TODO : 유저가 로그인을 했을 때, 회원정보를 데이터베이스에서 확인하고,
    const { email, password } = req.body
    users.findOne({ where: { email: email } }).then((result) => {
      if (result) {
        //회원가입 되어있는 경우 => return 값은 Promise <Model>
        res.cookie('session', result.id)
        res.status(200).json({id: `${result.id}`})
      } else {
        //회원가입이 안 되있는 회원일 경우 => return 값은 Promise<null>
        res.status(404).send('unvalid user')
      }
    })
  },
}

4)  GET /user/info 기능 구현

: 유저의 session을 이용하여, 데이터베이스에 있는 정보를 제공하도록 구현한다. 

유저의 session를 통해 정보를 가져오려 할 때는 어떤 경우의 수가 있을까?

지금까지의 flow를 보면 회원정보를 DB에서 확인하고 회원의 id를 session에 담아두도록 했다. 즉, 정상 authorized가 된 유저( = 로그인이 된 유저)라면, 해당 유저의 session가 생성되었을 것이고 그 id를 통해 db에서 유저정보에 접근할 수 있을 것이다. 

  • 1. 유저가 로그인되어서 session이 있을 때 => 데이터베이스에 있는 정보를 제공하도록 구현
  • 2. 유저가 로그인 거부 되어서 session이 없을 때 => (statusCode: 401, res.text: 'need user session')

=> 이 두가지 경우를 모두 표현할 수 있는 DB 접근 메서드는?(즉, sequelize 메서드는?) 위와 마찬가지로 findOne

 

cookie-parser 사용했을 때 

const { users } = require('../../models')

module.exports = {
  get: (req, res) => {
    // TODO : 유저의 session을 이용하여,데이터베이스에 있는 정보를 제공하도록 구현하세요.
    //cookie정보는 req.headers.cookie에 문자열 형태로 있으니까 parsing이 필요
    //이때 cookie-parser가 사용
    console.log('cookie 정보', req.headers.cookie) //cookie-parser 사용 x
    console.log('cookie 정보2', req.cookies) //cookie-parser 사용 o

    if (req.cookies.session) {
      users.findOne({ where: { id: req.cookies.session } }).then((result) => {
        res.status(200).json(result)
      })
    } else {
      res.status(401).send('need user session')
    }
  },
}

5) POST /user/signout 기능 구현

: 유저가 로그아웃했을 때, session 정보를 없애고, '/'로 redirect할 수 있도록 구현한다. 

sailsjs.com/documentation/reference/response-res/res-clear-cookie 

www.geeksforgeeks.org/express-js-res-clearcookie-function/

module.exports = {
  post: (req, res) => {
    // TODO : 유저가 로그아웃했을 때, session 정보를 없애고,'/'로 redirect할 수 있도록 구현하세요.
    console.log('req.cookies.session: ', req.cookies.session)

    if (req.cookies.session === undefined) {
      res.redirect('/')
    } else {
      // res.clearCookie('session', { path: '/' }) //test: {"x-powered-by":"Express","vary":"Origin","access-control-allow-credentials":"true","set-cookie":["session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"],"date":"Sun, 13 Sep 2020 11:39:28 GMT","connection":"close","content-length":"0"}
      // res.end()

      res.setHeader('Set-Cookie', `session='';max-age=0`)//test: {"x-powered-by":"Express","vary":"Origin","access-control-allow-credentials":"true","content-type":"text/html; charset=utf-8","content-length":"7","etag":"W/\"7-Qqj2Udef0AXurAYS32RCuYOgEYQ\"","date":"Sun, 13 Sep 2020 11:44:54 GMT","connection":"close"}
      res.redirect('/')
    }
  },
}

POST /user/signout 기능 구현: Set-Cookie의 max-age=0 사용 결과와 res.clearCookie 사용 결과 비교

 

Reference

expressjs.com/en/resources/middleware/cookie-parser.html

sequelize.org/master/manual/model-querying-finders.html#-code-findorcreate--code- 

sequelize.org/master/class/lib/model.js~Model.html#static-method-findOrCreate

sailsjs.com/documentation/reference/response-res/res-cookie

www.geeksforgeeks.org/express-js-res-clearcookie-function/

www.geeksforgeeks.org/express-js-res-clearcookie-function/

 

'Dev > SW Engineering' 카테고리의 다른 글

[TDD] TDD 시작하기 (Unit Test, Jest)  (0) 2021.02.22
40. Authentication - OAuth 2.0  (0) 2020.09.14
37. Authentication - Session  (0) 2020.09.06
36. Authentication - Cookie  (0) 2020.09.06
35. Authentication - Authentication & Authorization  (0) 2020.09.06