React Blog (2)

Auth Logic

User와 관련된 auth 로직에 관해 정리한다.

회원가입(register)

사용자는 브라우저의 form 태그로부터 회원가입에 필요한 정보를 받는다. 전달받은 정보를 검증하기 위한 schema를 작성한다. schema 작성을 위해 joi을 사용한다.

yarn add joi
import Joi from 'joi'
export const register = async (req, res) => {
    // 스키마를 작성하여 form 데이터를 검증하고 에러 발생시, 에러를 처리한다.
    const schema = Joi.object().keys({
        /*
        필드: Joi.string().min(3).max(20),
        */
    })
    const result = schema.validate(req.body)

    if (result.error) {
        // return error message
    }
}

joi가 아닌 mongoose의 validator를 이용할 수도 있지만 default validator는 min, max 정도 밖에 지원하지 않고 custom validator를 작성하면 코드의 양이 많아지기 때문에 controller 단에서 joi를 사용하는게 더욱 깔끔한 것 같다.

// 중복검사
const exists = await User.findOne{(username)}
if (exists) {
    return res.status(409).end()
}
const {username, password} = req.body
const user = new User({
    username
})
// encrypt password
await user.setPassword(password);
// 데이터베이스에 유저 데이터 저장.
await user.save();

Model.prototype.save()

Saves this document by inserting a new document into the database if document.isNew is true, or sends an updateOne operation with just the modified paths if isNew is false.

데이터베이스에 유저 데이터를 저장하기 위하여 다음과 같은 과정을 거쳤다.

  1. form validation 작성(joi 이용)
  2. username (또는 이메일)을 이용하여 중복검사
  3. encrypt password

JWT

로그인 상태를 구현하기 위해 사용.

두 가지 인증 시스템.

토큰 기반 인증 시스템

  1. 사용자가 로그인을 하면 서버는 토큰을 발급
  2. 이 후 사용자는 토큰과 함께 서버에 요청
  3. 서버는 토큰을 검사
  4. 토큰이 유효하면 응답

jasonwebtoken을 이용하여 토큰을 생성해야 한다.

토큰을 생성하는 함수를 유저 모델의 메소드로 등록한다.

import jwt from 'jsonwebtoken'
// token을 생성하고 반환한다.
UserSchema.methods.generateToken = function() {
    //jwt.sign({토큰에 담을 내용}, 시크릿 키, {옵션})
    const token = jwt.sign(
        {
        _id: this._id,
        username: this.username
        },
        config.jwtSecret,
        {
            expireIn: '7d'
        }
    )

    return token
}

이 후 로그인이나 회원가입할 때 토큰을 cookie에 담는다.

const register = async (req, res) => {
    try {
        // 1. form 내용 검증
        // 2. 중복검사
        // 3. 비밀번호 암호화
        // 4. 토큰 발급 <--
        const token = user.generateToken()
        return res.cookie('access_token', token, {
            maxAge: 1000 * 60 * 60 * 24 * 7,
            httpOnly: true
        })
        .status(200)
        .json(user.serialize())
    } catch (err) {
        // error 처리
    }
}

res.cookie(name, value [, options])

Sets cookie name to value. The value parameter may be a string or object converted to JSON.

req.cookies

When using cookie-parser middleware, this property is an object that contains cookies sent by the request. If the request contains no cookies, it defaults to {}.

res.cookie로 쿠키를 사용자에게 보내고 req.cookies로 사용자의 쿠키를 조회한다.

이제 사용자가 요청 시 쿠키에 담긴 토큰을 검사하여 로그인 상태인지 체크한다.

토큰을 검사하는 로직을 미들웨어로 등록한다.

토큰 검사는 jasonwebtokenverify()를 이용한다.


const validateJWT = async (req, res, next) => {
    const token = req.cookies.access_token
    if (!token) return next()
    try {
        // 1. token을 검사하여 decode 한다.
        // 2. 로그인한 유저 정보를 req객체에 담는다.
        const decoded = jwt.verify(token, config.jwtSecret)
        req.user = {
            _id: decoded._id,
            username: decoded.username
        }
        // ...

        // 이 미들웨어를 통과하면 req객체에 로그인한 유저 정보가 담기게 된다.
        return next()
    } catch (err) {
        return next()
    }
}

해당 미들웨어를 app단위의 미들웨어로 등록한다.

// express.js

// ...
app.use(validateJWT())

이 후 라우터에서 req.user를 조회하면 로그인한 상태인지 아닌지 알 수 있다.

// 로그인 상태를 검사하는 미들웨어
const checkLoggedIn = (req,res, next) => {
    if (!req.user) {
        return res.status(401).end()
    }
    next()
}

로그인이 필요한 라우트에 해당 미들웨어를 적용한다.


const posts = Router()

// 포스트 수정
posts.patch('/posts/:postId', checkLoggedIn, /* 다른 미들웨어*/)

// 포스트 삭제
posts.delete('/posts/:postId', checkLoggedIn, /* 다른 미들웨어*/)