프로젝트 1 - Auth

⭐프로젝트 1 - Auth

📕Auth

Auth 라우트를 왜 만들까??

우리가 특정 사이트를 간다고 했을 때 로그인이 된 유저들만 이용할 수 있고,

다른페이지는 누가나 사용할 수 있고 이런 차이점이 존재한다

그런것을 체크해주기 위해서 Auth 기능을 만드는 것이다

어떻게 구현을 하는지 설명하겠다

현재 우리는 토큰이 서버의 user 정보에 저장되어있고,

클라이언트의 쿠키에도 저장되어 있다

만약 사용자가 A사이트에서 B사이트로 넘어갈 때

이 사용자가 B사이트의 권한이 있는지

확인하기 위하여 클라이언트의 쿠키를 서버로 보낸다

쿠키 내에 토큰이 인코드 되어있는 상태인데 서버에서 받은 토큰을 디코드한다

지난 포스트에서 'secretToken'을 넣으면 user id가 나온다고 했었다

그럼 그 user id를 이용해서 그 user id의 DB에 토큰이 있다면 인증이 되는것이다

순서대로 설명하자면

  1. 쿠키에 저장된 토큰을 서버로 가져와서 복호화를 한다
  2. 복호화를 하면 user id가 나온다
  3. 그 user id로 DB에서 유저를 찾은 후 쿠키에서 받은 토큰이 있는지 확인한다

이렇게 진행된다

📘만들기

우리가 지금까지 라우터를 만들 때 그냥 바로 login, signup 이렇게 작성했는데

나중에 라우터가 많아지면 어느것과 관련된 정보인지 헷갈릴 수 있다

그래서 지금까지 우리가 만든 라우터는 유저와 관련된 라우터이기때문에

/api/users를 추가하도록 하겠다

그리고 auth라는 미들웨어를 사용할 것이기 때문에 middleware 폴더를 만들자

이후 폴더 안에 auth.js라는 파일을 만든뒤 아래의 코드를 작성한다

let auth = (req, res, next) => {
    //인증 처리 하는곳
}

module.exports = { auth }

그리고 index.js에서 사용할 수 있게끔 상단에

const { auth } = require('./middleware/auth')

를 추가해야한다

이제 auth.js 파일에서 인증처리를 해보자

클라이언트에서 쿠키 가져오기

지난번에 사용한 쿠키파서를 사용하면 된다

쿠키를 넣을 때 x_auth라는 이름으로 넣었다

let token = req.cookies.x_auth

이렇게 하면 토큰을 쿠키에서 가져온다

토큰 복호화(decode)

유저 모델이 필요해서 상단에 유저 모델을 추가하도록 하자

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

토큰을 만드는 메소드를 user.js에서 만든 것 처럼

토큰을 이용해서 찾는 메소드도 user.js에서 만들어야 한다

userSchema.statics.findByToken = function ( token, cb ){
   let user = this

   jwt.verify(token, 'secretToken', function(err, decoded){
       //userid를 이용해서 확인하기
   })
}

jwt.verify 는 기본적으로 jwt에서 제공하는 메소드이다

사이트에 가면 자세한 설명이 나온다

간단하게 설명하면 우리가 토큰을 만드는 방식을 전에 설명했지만 다시 말하자면

user id + 문자열 -> 토큰 이다

그래서 verify할 때 토큰을 넣고, 우리가 정한 문자열을 넣은 뒤에

콜백함수로 에러가 났을땐 에러, 제대로 되면 디코드한 값을 리턴하는 방식이다

userid로 유저찾고 인증하기

디코드한 값으로 현재 db에 있는 user에서 찾는 방법이다

userSchema.statics.findByToken = function ( token, cb ){
    let user = this

    jwt.verify(token, 'secretToken', function(err, decoded){
        user.findOne({ "_id": decoded, "token": token }, function (err, user){
            if(err) return cb(err)
            cb(null, user)
        })
    })
}

user에서 findOne 이라는 몽고db 메소드를 활용하여 유저를 찾는 것이다

지난번에 한 것과 비슷해서 이해가 될 것이다

이렇게 메소드를 다 만들었으면 auth.js로 가서 사용하도록 하자

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

let auth = (req, res, next) => {
    let token = req.cookies.x_auth

    User.findByToken(token, (err, user) => {
        if(err) throw err
        if(!user) return res.json({ isAuth: false, error: true })
        
        req.token = token
        req.user = user
        next()
    })
}

module.exports = { auth }

이렇게 findByToken 메소드를 활용한다

next()를 넣은 이유는 미들웨어이기 때문에 넘어가는게 없으면 계속 갇히게된다

index.js에서 req.token, req.user 정보를 받을 수 있다

app.get('api/users/auth', auth, (req, res) => {
  //미들웨어를 통과해 여기까지 왔다는 것은 Auth가 true 라는 의미
  
})

여기까지 왔다는 것은 Auth가 true라는 것이다

이것을 클라이언트에게 보여주기위해 조금의 코드를 추가하도록 하자

사용자의 정보를 띄워줄 것이다

여기서 role에 0이 있으면 일반사용자이고,

0이 아닌 다른 수가 있다면 관리자로 취급한다

app.get('/api/users/auth', auth, (req, res) => {
  //미들웨어를 통과해 여기까지 왔다는 것은 Auth가 true 라는 의미
  res.status(200).json({
    _id: req.user._id,
    isAdmin: req.user.role === 0 ? false : true,
    isAuth: true,
    email: req.user.email,
    name: req.user.name,
    role: req.user.role,
    image: req.user.image
  })
})

이렇게 코드를 작성해 주었다

이후 포스트맨을 사용하여 확인을 해보자


이렇게 잘 나온것을 확인할 수 있다

📗코드

auth.js

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

let auth = (req, res, next) => {
    let token = req.cookies.x_auth

    User.findByToken(token, (err, user) => {
        if(err) throw err
        if(!user) return res.json({ isAuth: false, error: true })
        
        req.token = token
        req.user = user
        next()
    })
}

module.exports = { auth }

user.js

const mongoose = require('mongoose')
const bcrypt = require('bcrypt')
const saltRounds = 10
const jwt = require('jsonwebtoken')

const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50
    },
    email: {
        type: String,
        trim: true,
        unique: 1
    },
    password: {
        type: String,
        minlength: 5
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String
    },
    tokenExp:{
        type: Number
    }
})

userSchema.pre('save', function(next){
    let user = this;

    if (user.isModified('password')){
        bcrypt.genSalt(saltRounds, function (err, salt) {
            if(err) return next(err)
            bcrypt.hash(user.password, salt, function (err, hash){
                if(err) return next(err)
                user.password = hash
                next()
            })
        })
    } else {
        next()
    }
})

userSchema.methods.comparePassword = function(plainPassword, cb) {
    bcrypt.compare(plainPassword, this.password, function(err, isMatch) {
        if(err) return cb(err)
        cb(null, isMatch)
    })
}

userSchema.methods.generateToken = function(cb){
    let user = this
    let token = jwt.sign(user._id.toHexString(), 'secretToken');

    user.token = token
    user.save(function(err, user){
        if(err) return cb(err)
        cb(null, user)
    })
}

userSchema.statics.findByToken = function ( token, cb ){
    let user = this

    jwt.verify(token, 'secretToken', function(err, decoded){
        user.findOne({ "_id": decoded, "token": token }, function (err, user){
            if(err) return cb(err)
            cb(null, user)
        })
    })
}

const User = mongoose.model('User', userSchema)

module.exports = { User }

index.js

const express = require('express')
const app = express()
const port = 3000
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const config = require('./config/key')
const cookieParser = require('cookie-parser')
const { auth } = require('./middleware/auth')
const { User } = require("./models/User")


app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())
app.use(cookieParser())

mongoose.connect(config.mongoURI
).then( () => console.log('MongoDB Connected'))
 .catch(err => console.log(err))

app.get('/', (req, res) => {
  res.send('Hello World! BooKi')
})

app.post('/api/users/signup', (req, res) => {
  //회원 가입 할 때 작성한 정보들을 가져와 DB에 넣어준다
  const user = new User(req.body)

  user.save((err, userInfo) => {
    if (err) return res.json({ success: false, err})
    return res.status(200).json({
      success: true
    })
  })
})

app.post('/api/users/login', (req, res) => {
  User.findOne({ email: req.body.email }, (err, user) => {
    if(!user) {
      return res.json({
        loginSuccess: false,
        message: "해당 이메일에 해당하는 유저가 없습니다."
      })
    }

    user.comparePassword(req.body.password, (err, isMatch) => {
      if(!isMatch){
        return res.json({
          loginSuccess: false,
          message: "비밀번호가 틀렸습니다."
        })
      }
      user.generateToken((err, user) => {
        if(err) {
          return res.status(400).send(err)
        }
                
        res.cookie("x_auth", user.token)
        .status(200)
        .json({
          loginSuccess: true,
          userId: user._id
        })
      })
    })
  })
})

app.get('/api/users/auth', auth, (req, res) => {
  //미들웨어를 통과해 여기까지 왔다는 것은 Auth가 true 라는 의미
  res.status(200).json({
    _id: req.user._id,
    isAdmin: req.user.role === 0 ? false : true,
    isAuth: true,
    email: req.user.email,
    name: req.user.name,
    role: req.user.role,
    image: req.user.image
  })
})

app.listen(port, () => {
  console.log(`http://localhost:${port}/`)
})

좋은 웹페이지 즐겨찾기