4
votes

I have created a login form that should redirect the user to a dashboard page in case he enters the right password and username. If the user tries to navigate to the dashboard url without being logged in the page should not display as it is a protected route. I am trying to send a jwt token when the user logs in, but that doesn't work I just get the Forbidden message when I log in so it seems that the token is not sent correctly, how can I send the jwt token and access the protected route once the user logs in successfully?

Here is my 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
This might help stackoverflow.com/questions/59241078/…user12948615
How are you sending the token to the server when the user logs in ?mousto090
You are expecting the token to be in header, but are doing anything in your front end Javascript to send the received token in the header?Tarun Lalwani
I uploaded old code by mistake, I updated my question now, I am trying to send it when the user passwords match in the else statement res.setHeader('Authorization', 'Bearer '+ token);Mahma Deva
Can you post your frontend code as well ,from where you are sending jwt to backendShubh

4 Answers

3
votes

see this example, i use middleware(checkAuthLogin), this code contains all thing for your question:

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};
2
votes

Your issue is that your token variable is only accessible inside of the callback to the jwt.sign call, so when you try to do this here res.setHeader('Authorization', 'Bearer '+ token);, it won't know what variable you're referring to, hence the undefined error. By the way, if you're going to use jwt.sign asynchronously, then the code that uses it needs to also be inside of the callback, otherwise synchronous code outside of the callback will likely execute first (and thus not be able to access any results of the asynchronous code) as the asynchronous callback executes in the background. The solution here is to either switch your usage to a synchronous usage or place your response code inside of the callback. Also, calling res.json will end the response so I'm not sure what exactly you're trying to accomplish with the multiple response calls

Synchronous version:

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');
    }

});

Asynchronous version:

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')
        }
    });

});

In these examples, I took out res.json({ token }) because you can't use res.json and then perform a redirect, but modify those parts however best fits your code. On another note, you probably don't want to include the password in your token because while JWTs (when using the default/standard algorithms which do not include encryption) are cryptographically guaranteed to be unmodifiable, they are still readable

1
votes

I have one solution to send jwt token, but you will need to install one more package. If you think it worth maybe you can follow.

I use express only for backend api. But you can use the same logic applied here to your application.

The lib you will need to install is the express-jwt

It handles routes to block access to endpoint that need authentication.

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);
    }
});

Now in your frontend put the authorization token sent by this route in cookies or store in client-side. Do the next request with the header authorization for the secure dashboard route.

0
votes

I think the problem in the sign in controller function

  • you must check first if the user have the correct password before attempting to send him a token

  • you should save the result of jwt sign function in a variable to send back to the user in case he has the right credintials.

  • It make no sense to send the password again to the user , only the username is needed

you can try this :


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')

});