[Error] JsonWebTokenError: jwt must be provided

changheeee·2023년 10월 15일
0

리액트에서 인증(로그인) 상태에 따라 진입여부를
Auth를 HOC(Higher-order component)를 이용하여 구현하고

페이지가 작동하는 것을 확인하고 다시 실행해보니 프록시 에러가 발생합니다
이것저것 해보다가 갑자기 에러가 사라지고 모든 기능이 제대로 작동해서 서버, 클라이언트, 텍스트에디터, 크롬 등등.. 을 종료하고 다시 실행하니 프록시 에러가 다시 발생했습니다.


에러확인

[브라우저 콘솔 확인]]

콘솔창의 api/hello OK! 을 통해 서버와 클라이언트가 실행되고 LandingPage에서의 네트워크 요청은 정상적으로 이루어 졌고,


콘솔창의 GET http://localhost:3000/api/users/auth 부분에서의 요청이 제대로 이루어지지 않았다는 것을 알 수 있었습니다

[터미널 확인]

JsonWebTokenError: jwt must be provided
JWT가 제공되어야 하는데 제공되지 않았다는 것을 알 수 있었습니다


원인

JsonWebTokenError: jwt must be provided
이 오류는 JWT 토큰이 제공되지 않았을 때 발생합니다. 즉, JWT 라이브러리가 토큰을 해석하려고 시도했지만, 전달된 토큰이 없거나 정의되지 않았기 때문에 이런 오류가 발생한 것입니다.

[auth.js 수정 전]

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

let auth = async (req, res, next) => {
    try {
        // 클라이언트 쿠키에서 토큰 가져옴
        let token = req.cookies.user_auth
        // 토큰을 복호화 후 유저 찾음
        const user = await User.findByToken(token);
        if (!user) {
            return res.json({ isAuth: false, error: true });
        }

        req.token = token;
        req.user = user;
        next()
    } catch (err) {
        throw err
    }
};

module.exports = { auth }

해당 문제는 auth.js 파일에서 발생했습니다.
여기서 User.findByToken(token) 메소드를 호출하면서
token을 인자로 전달하는데, 이 tokenreq.cookies.user_auth에서 가져옵니다.

위 코드에서는 req.cookies.user_auth 값의 존재 여부를 검증하지 않고 바로 사용하였습니다. 따라서 클라이언트가 user_auth 쿠키를 보내지 않았다면
undefined 값을 token으로 사용하게 되고, 그 결과 "jwt must be provided"라는 에러가 발생한 것입니다.

let token = req.cookies.user_auth // undefined일 수 있음
const user = await User.findByToken(token); // 여기서 에러 발생


해결

1.auth.js 수정

[auth.js 수정 후]

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

const auth = async (req, res, next) => {
    try {
        // 클라이언트 쿠키에서 토큰 가져옴
        const token = req.cookies.user_auth;

        if (!token) {
            return res.json({ isAuth: false, error: true });
        }

        // 토큰을 검증하고 사용자를 찾음
        const user = await User.findByToken(token);
        if (!user) {
            return res.json({ isAuth: false, error: true });
        }

        req.token = token;
        req.user = user;
        next();
    } catch (err) {
        throw err;
    }
};

module.exports = { auth };

문제를 해결하기 위해 수정된 코드에서는 다음과 같이 user_auth 쿠키의 존재 여부를 먼저 확인합니다

const token = req.cookies.user_auth;

if (!token) {
    return res.json({ isAuth: false, error: true });
}

위 코드에 의해 클라이언트가 user_auth 쿠키를 제공하지 않았을 경우 요청 처리를 중단하고 { isAuth: false, error: true } 응답을 보내게 됩니다.


2.User.js 수정

[User.js 수정 전]

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

const userSchema = mongoose.Schema({
   ...
})

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) {
    console.log(plainPassword)
    return new Promise((resolve, reject) => {
        bcrypt.compare(plainPassword, this.password, (err, isMatch) => {
            if (err) {
                reject(err)
            } else {
                resolve(isMatch)
            }
        })
    })
}

userSchema.methods.generateToken = function () {
    const user = this
    const token = jwt.sign({ userId: user._id.toHexString() }, "secretToken")

    user.token = token
    return user.save()
        .then(() => token)
}

userSchema.statics.findByToken = function (token) {
    const user = this

    return new Promise((resolve, reject) => {
        jwt.verify(token, 'secretToken', (err, decoded) => {
            if (err) {
                reject(err)
            }

            user.findOne({ '_id': decoded.userId, 'token': token })
                .then(user => {
                    resolve(user)
                })
                .catch(err => {
                    reject(err)
                })
        })
    })
}

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

module.exports = { User }

[User.js 수정 후]

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

// 'secretToken' 값 설정 (임의로 선택한 시크릿 토큰)
const secretToken = 'yourSecretToken';

// 사용자 스키마 정의
const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50,
    },
    email: {
        type: String,
        trim: true, // 공백 제거, 예: "ckd gml@naver.com" -> "ckdgml@naver.com"
    },
    password: {
        type: String,
        minlength: 5
    },
    lastname: {
        type: String,
        maxlength: 50
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String
    },
    tokenExp: {
        type: Number
    }
})

// 'save' 이벤트 전(pre)에 비밀번호 암호화
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) {
    console.log(plainPassword)
    return new Promise((resolve, reject) => {
        bcrypt.compare(plainPassword, this.password, (err, isMatch) => {
            if (err) {
                reject(err)
            } else {
                resolve(isMatch)
            }
        })
    })
}

// JWT 토큰 생성 메서드
userSchema.methods.generateToken = function () {
    const user = this
    const token = jwt.sign({ userId: user._id.toHexString() }, secretToken)

    user.token = token
    return user.save()
        .then(() => token)
}

// JWT 토큰 검증 및 사용자 찾기 메서드
userSchema.statics.findByToken = function (token) {
    const user = this

    return new Promise((resolve, reject) => {
        jwt.verify(token, secretToken, (err, decoded) => {
            if (err) {
                reject(err)
            }
            User.findOne({ '_id': decoded.userId, 'token': token }) // 'User'로 수정
                .then(user => {
                    resolve(user)
                })
                .catch(err => {
                    reject(err)
                })
        })
    })
}
// User 모델 정의
const User = mongoose.model('User', userSchema)

module.exports = { User }

User.js의 변경점은 다음과 같습니다

1. secret key를 변수로 선언

이전코드

const secretToken = 'yourSecretToken';

수정 전 코드에서는 JWT 토큰을 생성할 때 "secretToken"이라는 하드 코딩된 문자열을 사용하여 시크릿 토큰을 정의했습니다.


수정된 코드

const secretToken = 'yourSecretToken';
const token = jwt.sign({ userId: user._id.toHexString() }, secretToken);

수정된 코드에서는 const secretToken = 'yourSecretToken';을 사용하여 시크릿 토큰을 변수로 정의했습니다. 이 변수를 사용하여 JWT 토큰을 생성합니다.


2. 'User' 모델 직접 참조

User.findOne({ '_id': decoded.userId, 'token': token })

이전 코드에서는 user를 모델 이름으로 사용하고 user.findOne으로 사용자를 찾았습니다. 수정된 코드에서는 User 모델을 정확히 사용하고 User.findOne으로 사용자를 찾습니다. 이렇게 수정함으로써 모델과 메서드를 일관되게 사용하게 되며, 오타나 오류를 방지할 수 있습니다.

이제 잘 작동한다 ㅠㅠ

0개의 댓글