如何将jwt令牌发送到node.js中的受保护路由

时间:2020-05-06 15:16:16

标签: node.js express jwt

我创建了一个登录表单,如果用户输入正确的密码和用户名,该表单应将用户重定向到仪表板页面。如果用户尝试在未登录的情况下导航至仪表板URL,则该页面不应显示,因为它是受保护的路由。我试图在用户登录时发送jwt令牌,但这不起作用,登录时我只是收到禁止消息,因此似乎令牌发送不正确,我该如何发送jwt令牌并进行访问用户成功登录后该受保护的路由?

这是我的server.js:

const express = require('express');
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
let Post = require('./models/post.model.js');
const app = express();
const cors = require('cors');
require('dotenv').config();

app.use(cors());
app.use("/assets", express.static(__dirname + "/assets"));
app.use(bodyParser.urlencoded({ extended: true }));
const BASE_URL = process.env.BASE_URL;

const PORT = process.env.PORT || 1337;
mongoose.connect(BASE_URL, { useNewUrlParser: true, useUnifiedTopology: true })

const connection = mongoose.connection;

connection.once('open', function () {
    console.log('Connection to MongoDB established succesfully!');
});

app.set('view-engine', 'ejs');

app.get('/', (req, res) => {
    res.render('index.ejs');
});

app.post('/', (req, res) => {
    let username = req.body.username;
    let password = req.body.password;

    const user = {
        username: username,
        password: password
    }

    jwt.sign({ user }, process.env.SECRET_KEY, (err, token) => {
        res.json({
            token
        })
    });

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) {
        res.json('Invalid credentials');
    } else {
        res.setHeader('Authorization', 'Bearer '+ token);
        res.redirect('/dashboard')
    }

});

app.get('/dashboard', verifyToken, (req, res) => {
    jwt.verify(req.token, process.env.SECRET_KEY, (err, authData) => {
        if (err) {
            res.sendStatus(403);
        } else {
            res.sendStatus(200);
        }
    });
    res.render('dashboard.ejs');
});

app.get('/dashboard/createPost', verifyToken, (req, res) => {
    res.render('post.ejs');
});

app.post('/dashboard/createPost', async (req, res) => {
    let collection = connection.collection(process.env.POSTS_WITH_TAGS);
    res.setHeader('Content-Type', 'application/json');
    let post = new Post(req.body);
    collection.insertOne(post)
        .then(post => {
            res.redirect('/dashboard')
        })
        .catch(err => {
            res.status(400).send(err);
        });
});

// TOKEN FORMAT
// Authorization: Bearer <access_token>

//Verifing the Token
function verifyToken(req, res, next) {
    // Get auth header value
    const bearerHeader = req.headers['authorization'];
    // Check if bearer is undefined
    if (typeof bearerHeader !== 'undefined') {
        // Spliting the bearer
        const bearer = bearerHeader.split(' ');
        // Get token from array
        const bearerToken = bearer[1];
        // Set the token
        req.token = bearerToken;
        // Next middleware
        next();

    } else {
        // Forbid the route
        res.sendStatus(403);
    }

}

app.listen(PORT);

4 个答案:

答案 0 :(得分:2)

参见此示例,我使用中间件(checkAuthLogin),此代码包含您所提出问题的所有内容:

index.js:

const express = require('express');
const app = express();
require('./db/mongoose');

const userRouter = require('./routers/user');


app.use(express.json());
app.use(userRouter);


app.listen(3000, ()=> { 
    console.log('Server is up on port ', 3000)
});

db / mongoose.js:

const mongoose = require('mongoose');

mongoose.connect("mongodb://127.0.0.1:27017/db-test" {
    useNewUrlParser : true,
    useCreateIndex : true,
    useFindAndModify : false,
    useUnifiedTopology: true
});

routers / user.js:

const express = require('express');
const router = new express.Router();
const RootUser = require('../models/root-user');
const {checkRootLogin} = require('../middleware/checkAuthLogin');

router.post('/createrootuser', async (req, res) => {

    const updates = Object.keys(req.body);
    const allowedUpdatesArray = ['name', 'password'];
    const isValidOperation = updates.every((update) => allowedUpdatesArray.includes(update));

    if (!isValidOperation) {
        return res.status(400).send({error: 'Invalid Request Body'})
    }

    const rootUser = new RootUser(req.body);

    try {
        await rootUser.save();
        // sendWelcomeEmail(user.email, user.name)
        const token = await rootUser.generateAuthToken();
        //console.log(user)
        res.status(201).send({rootUser, token});
    } catch (e) {
        res.status(400).send(e)
    }

});

//use this middleware(checkRootLogin) for check root user can access this function
router.post('/rootconfig', checkRootLogin, async (req, res) => {

        res.status(200).send({success: 'success add root config'})

});

module.exports = router;

model / root-user.js:

const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');    

const userRootSchema = new mongoose.Schema({
    name: {
        type : String,
        required: true,
        unique : true,
        trim : true,
        lowercase : true,
    },
    password: {
        type : String,
        required: true,
        unique : true,
        trim : true,
        lowercase : true,
        minlength : 6,
        validate (value) {
            //if (validator.contains(value.toLowerCase(), 'password')){
            if (value.toLowerCase().includes('password')){
                throw new Error('Password can not contained "password"')
            }
        }
    },

    tokens : [{
        token : {
            type : String ,
            required : true
        }
    }],

}, {
    timestamps: true
});


userRootSchema.methods.generateAuthToken = async function(){

    const root = this;
    // generate token
    try {
        // const token = jwt.sign({ _id : user._id.toString()}, process.env.JWT_SECRET);
        const token = jwt.sign({ _id : root._id.toString()}, "test");
        // add token to user model
        root.tokens = root.tokens.concat({ token });
        await root.save();
        return token
    } catch (e){
        throw new Error(e)
    }

};



userRootSchema.pre('save', async function(next){
    // this give ccess to individual user
    const user = this;

    if (user.isModified('password')){
        user.password = await bcrypt.hash(user.password, 8)
    }
    next()

});

const UserRoot = mongoose.model('UserRoot', userRootSchema);

module.exports = UserRoot;

middleware / checkAuthLogin.js:

const jwt = require('jsonwebtoken');
const RootUser = require('../models/root-user');  

const checkRootLogin = async (req, res, next) => {
    try {
        const token = req.header('Authorization').replace('Bearer ', '');
        // const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const decoded = jwt.verify(token, "test");

        const rootUser = await RootUser.findOne({_id: decoded._id, 'tokens.token': token});

        if (!rootUser) {
            throw new Error("User cannot find!!");
        }

        req.token = token;
        req.rootUser = rootUser;
        req.userID = rootUser._id;
        next()
    } catch (e) {
        res.status(401).send({error: 'Authentication problem!!'})
    }
};

module.exports = {checkRootLogin};

答案 1 :(得分:2)

您的问题是您的token变量只能在jwt.sign调用的回调内部访问,因此,当您尝试在res.setHeader('Authorization', 'Bearer '+ token);进行此操作时,它将不知道您要引用的变量,因此未定义错误。顺便说一句,如果您要异步使用jwt.sign,则使用它的代码也必须在回调内部,否则,回调外部的同步代码很可能会首先执行(因此无法以访问异步代码的任何结果),因为异步回调在后台执行。这里的解决方案是将您的用法切换为同步用法,或者将您的响应代码放入回调中。另外,致电res.json将结束响应,所以我不确定您要通过多次响应调用到底要完成什么

同步版本:

app.post('/', (req, res) => {
    let username = req.body.username;
    let password = req.body.password;

    const user = {
        username: username,
        password: password
    };

    let token = undefined;
    try {
        token = jwt.sign({ user }, process.env.SECRET_KEY);
    } catch (e) {
        // handle error
    }

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) {
        res.json('Invalid credentials');
    } else {
        res.setHeader('Authorization', 'Bearer '+ token);
        res.redirect('/dashboard');
    }

});

异步版本:

app.post('/', (req, res) => {
    let username = req.body.username;
    let password = req.body.password;

    const user = {
        username: username,
        password: password
    }

    jwt.sign({ user }, process.env.SECRET_KEY, (err, token) => {
        if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) {
            res.json('Invalid credentials');
        } else {
            res.setHeader('Authorization', 'Bearer '+ token);
            res.redirect('/dashboard')
        }
    });

});

在这些示例中,我删除了res.json({ token }),因为您不能使用res.json然后执行重定向,但是修改这些部分却最适合您的代码。另外,您可能不希望在令牌中包含密码,因为尽管JWT(使用不包含加密的默认/标准算法时)在密码上保证不可修改,但它们仍是可读的

答案 2 :(得分:1)

我有一种发送jwt令牌的解决方案,但是您将需要再安装一个软件包。如果您认为值得,那么可以遵循。

我仅将Express用于后端api。但是您可以对您的应用程序使用相同的逻辑。

您需要安装的库是express-jwt

它处理路由以阻止对需要身份验证的端点的访问。

server.js

require('dotenv').config()
const express = require('express');
const logger = require('morgan');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const expressJwt = require('express-jwt');

const app = express();

cors({ credentials: true, origin: true });
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/secure', expressJwt({ secret: process.env.SECRET }));
app.use(require('./server/index'));

app.get('/secure/dashboard') => {
    //now you can only access this route with authorization header
    //prependending the '/secure/ to new routes should make them return 401 when accessed without authorization token
    //accessing this route without returns 401.
    //there is no need to validate because express-jwt is handling.
    console.log(res.user)//should print current user and pass signed with token
    res.render('dashboard.ejs');
});

app.post('/', (req, res) => {
    let username = req.body.username;
    let password = req.body.password;
    //jwt.sign({ user }, process.env.SECRET_KEY, (err, token) => {
    //    res.json({
    //        token
    //    })
    //});
    //shouldn't sign json here, because there is no guarantee this is a valid
    //username and password it can be an impostor

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) {
        res.json('Invalid credentials');
    } else {
        const user = {
           username: username,
           password: password
        };
        const tk = {};
        tk.token = 'Bearer ' + jwt.sign(user, process.env.SECRET_KEY, { expiresIn: 1800 });//expires in 1800 seconds
        res.status(200).json(tk);
    }
});

现在在您的前端中,将通过此路由发送的授权令牌放入cookie或存储在客户端中。 使用安全仪表板路线的标题授权执行下一个请求。

答案 3 :(得分:0)

我认为登录控制器功能存在问题

  • 您必须先检查用户密码是否正确,然后再尝试向其发送令牌

  • 您应该将jwt sign function的结果保存在变量中,以防用户使用正确的密码发送给用户。

  • 将密码再次发送给用户没有任何意义,只需要用户名

您可以尝试以下方法:


app.post('/', (req, res) => {

    const {username , password} = req.body;

    if (username !== process.env.USER_NAME && password !== process.env.USER_PASSWORD) {
        return res.json('Invalid credentials');
    }


    const token = jwt.sign({username:username }, SECRET)
    res.setHeader('Authorization', token);
    res.redirect('/dashboard')

});