My node JS application involves setting a Jason Web Token to the response header at login so that I can use this JWT to verify the user when accessing restriced pages.
My question is how to get this header to persist. Currently I am setting the header at registration and login routes using code such as this:
// Login Handle
router.post("/login", async (req, res, next) => {
const user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).send("Email or password is incorrect");
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send("Invalid email or password.");
const token = user.generateAuthToken();
res.set('x-auth-token', token).redirect('/dashboard');
});
Here is my authentication middleware that I am trying to get working:
function auth(req, res, next) {
const token = req.header('x-auth-token');
if(!token) return res.status(401).send("Access denied. No token provided");
// if the token is valid it will return the decoded payload
try {
const decoded = jwt.verify(token, jwtPrivateKey);
req.user = decoded;
next();
}
catch(ex) {
res.status(400).send("Invalid token.");
}
I can see in the network tab on chrome dev tools that the header is correctly being set with the JWT. However, as soon as the page is refreshed or I visit a different page the header disappears.
Do I need to use res.set('x-auth-token', token).render('foo') on every route? It seems like this wouldn't be a great solution.
Can anyone tell me what I am doing wrong?
Related
I wanted to know what is the best way to add authorization header in nodejs app so that client req header gets that header set from the server on login. That's, once a user logs in, server should set an authorization header with the token, hence let that header persist on all routes, so that if it is present on the client, the server can allow user to visit routes.
I have seen alot of talk about adding it as a cookie but I prefer authorization header for this scenario. In between I would want to make sure on logout token is destroyed.
I am using JWT, Passport, express, nodejs for this app. Any help would be appreciated.
let access;
/\*\*
* #route POST api/auth/login
* #desc Login user and return token
* #access public
\*/
exports.login = async (req, res, next) =\> {
try {
// Make sure this account exists
const user = await Users.findOne({ email: req.body.email }).select(
'+password',
);
if (!user)
return res.status(401).json({
message: 'Invalid email or password',
});
// Validate password
const validate = await bcrypt.compare(req.body.password, user.password);
if (!validate)
return res.status(401).json({ message: 'Invalid email or password' });
// Make sure user has been verified
if (!user.isVerified)
return res.status(401).json({
type: 'no-verified',
message: 'Your account has not been verified.',
});
const token = user.generateJWT();
access = token;
// Login user, write token, and send back user
const { password, ...data } = user._doc;
res.status(200).json({ hash: token });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
};
exports.addHeader = (req, res, next) =\> {
console.log(access);
if (!access) {
console.log('token: undefined');
}
req.headers.authorization = 'Bearer ' + access;
next();
};
// In index.js
app.all('\*', authController.addHeader);
Tried that, could not get it to work.
I am performing authentication and authorization using JWT and building rest apis to connect ejs and backend. After getting a person authenticated i am rendering to the blogger page of that user but when i clink on add block it says no token is passed but when i am doing it using postman then there is no issue it is getting token then.
this is my code of rendering a blogger page after authentication:
router.post('/', async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({email:req.body.email});
if (user) return res.status(400).send("user already registered");
user = new User(_.pick(req.body,['name','email','password']));
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password,salt);
await user.save();
// const token= user.generateAuthToken();
// res.header('x-auth-tocken',token).send({name:user.name, user:user._id,token:token});
const token = jwt.sign({_id:this._id},config.get('jwtPrivateKey'));
let blogs = await blogss.find();
res.header('x-auth-token',token).render('bhome',{blogs:blogs,user:user});
})
and this is my auth middleware:
module.exports = function (req ,res, next) {
const token = req.header('x-auth-token');
console.log(req.header('x-auth-token'));
console.log('me here in auth');
if(!token) return res.status(401).send('access denied because there is no token');
try {
const decoded = jwt.verify(token,config.get('jwtPrivateKey'));
req.user = decoded;
next();
} catch (ex) {
res.status(400).send('invalid taken');
}
}
and this is the route after authentication which says token is not availible:
router.get('/addblog', auth, (req, res)=>{
res.render('addblog');
});
The way you are handling the header is wrong and based on the limited code you provided, it seems to be the reason for possible error. Instead of this doing the hard way, try using this gist and implement it as a util function in your frontend
I am trying to understand how app.get() works in calling functions when trying to switch between web pages.
I've created a user-login page that assigns a token to the user and checks it in a function.
I use app.post('/login', login); to call the login function which sends the user object to the server. After creating the token I'm hoping to then render the next page in a function after checking the token. (See code below)
However, I don't really understand how app.get('/', checkToken, getProfilePage) is then called. As I don't think it ever gets called.
I've looked at some websites that explain about HTTP requests but I'm struggling to find out, how it all links together inside app.js.
App.js:
app.post('/login', login);
app.get('/', authorize.checkToken, getProfilePage);
function login(req, res, next) {
userService.login(req.body, (err, user) => {
if (err) {
res.redirect('error.ejs');
console.log(error.message);
}
console.log(user);
if (!user) {
res.status(400).json({ success: false, message: 'Username or
password is incorrect' });
}
else {
res.json(user);
}
})
}
The next login function assigns the token and is used above as middleware:
function login({ username, password }, callback) {
grabUsers((err, users) => {
let user = users.find(u => u.username === username && u.password
=== password);
if (user) {
const token = jwt.sign({ username: username }, config.secret,
{ expiresIn: '24h'
}
);
const { password, ...userWithoutPassword } = user;
user = {
...userWithoutPassword,
success: true,
message: 'Authentication successful!',
token: token
}
};
callback(null,user);
})
}
Inside authorize.js:
let jwt = require('jsonwebtoken');
const config = require('./config.js');
let checkToken = (req, res, next) => {
console.log("check token running...");
let token = req.headers['x-access-token'] ||
req.headers['authorization']; // Express headers are auto
converted to lowercase
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}
if (token) {
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.json({
success: false,
message: 'Token is not valid'
});
} else {
req.decoded = decoded;
next();
}
});
} else {
return res.json({
success: false,
message: 'Auth token is not supplied'
});
}
};
module.exports = {
checkToken: checkToken }
getProfilePage function:
module.exports = {
getProfilePage: (req, res) => {
res.render('profile.ejs');
}
}
So my login form posts to /login and then once it has been verified I would like to check the token and then call getProfilePage. But how do I call the app.get() after the login data has been posted and authenticated?
You don't call a route from your app, what you need to do is redirect to it : res.redirect('/');
I believe there is a problem with how you try to authenticate a user. Seems to me you send a token in authorization header which is a common practice when you accessing API. Not sure how/when you generate the token and set this header though...
Anyway, this approach is good for authorization but not so good for authentication.
After successful /login request you should set the authentication cookie (user session). To simplify, you can just generate a JWT with userId encoded into it and use it as the value for this cookie (let's call it user-session).
Now after that each time user makes a request the cookie will be sent with it and you can decode the userId from this JWT. (Same thing, but now you'll take token from req.cookies['user-session'] instead of req.headers['authorization']).
But how do I call the app.get() after the login data has been posted and authenticated?
You can either navigate to this page from the client right after you receive successful /login response if you're using AJAX (i.e. window.location.replace('/')) or you can do res.redirect('/') instead of res.json(user) on successful login if you submit the HTML form without AJAX.
Redirect forces a browser to immediately make another request to the URL you specify and by that time you'll have user-session cookie set, i.e. you'll be able to retrieve userId and return correct profile page.
I have a small node application that uses express. In order for a user to submit form data, they must log in. When they log in, they get a json web token that should get passed to the auth middleware to verify that they can submit the form.
I am running into trouble sending the JWT to the auth middleware.
The Authorization when the user logs in and then gets sent to the page to submit the form looks like this:
router.post('/auth', async (req, res, next) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).send('Invalid email or password.');
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send('Invalid email or password.');
const token = user.generateAuthToken();
res.set({'x-auth-token': token }).render('../views/index/form', { 'x-auth-token': token })
;});
The route for the form submission looks like this:
router.post('/rates', auth, async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let rates = new Rates({
// deleted to save space
});
rates = await rates.save();
res.send(rates);
});
As you can see in order to post, it has to go through the auth middleware first which looks like this:
const config = require('config');
const jwt = require('jsonwebtoken');
function auth(req, res, next) {
const token = req.header('x-auth-token');
if (!token) return res.status(401).send('Access denied. No token provided.')
try {
const decoded = jwt.verify(token, config.get('jwtPrivateKey'));
req.user = decoded;
next();
}
catch (ex) {
res.status(400).send('Invalid token.');
}
}
module.exports = auth;
I get the "Access denied. No token provided." every single time. When I do a post request to rates in Postman and set the headers there, it works fine.
I am sending this form data through a page rendered with express handlebars.
I'm not sure if I am using res.set the wrong way, or if something gets lost when the handlebars form view gets rendered?
I can see the x-auth-token when I log in on the front end, get directed to the form page, and view the headers in chrome inspector. But once I try to submit the form data for the rates in the front end, the auth token is lost and it fails at the auth middleware.
There has to be something I'm just not seeing.
Here is my code on the page that is sending the form data:
<script>
var formElement = document.querySelector("form");
var header = XMLHttpRequest.getResponseHeader("x-auth-token");
var xhr = new XMLHttpRequest();
xhr.open('POST', '/rates', true);
xhr.setRequestHeader("x-auth-token", header);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if(this.readyState == XMLHttpRequest.DONE && this.status = 200) {
console.log("success");
} else {
console.log("error sending")
}
}
xhr.send(new FormData(formElement));
</script>
I have created a project in nodejs to check how jwt works. I have put authentication on a route I am able to validate with postman. Although I want that validated route to render a page if the user is logged in. I am using handlebars.js for templating. Can anyone help me how to make this happen.
index.js (Route)
var ctrlUsers = require('../controllers/users.controllers.js');
router
.route('/users/login')
.post(ctrlUsers.login);
app.use(ctrlUsers.authenticate);
.get(ctrlUsers.authenticate,ctrlUsers.showaddress);
module.exports = router
users.controller.js
module.exports.login = function(req, res) {
console.log('logging in user');
var username = req.body.username;
var password = req.body.password;
User.findOne({
username: username
})
.exec(function(err, user) {
if (err) {
console.log(err);
res.status(400).ender('error');
}
else {
if (bcrypt.compareSync(password, user.password)) {
console.log('User found', user);
var token = jwt.sign({ username: user.username }, 's3cr3t', { expiresIn: 3600 });
console.log("Token1" + token);
res
.status(200)
.redirect('/');
} else {
res
.status(401)
.render('error');
}
}
});
};
module.exports.showaddress = function(req, res) {
res.render('index');
console.log("I am in");
console.log(req.user);
}
module.exports.authenticate = function(req, res, next) {
var headerExists = req.headers.authorization;
console.log(headerExists);
if (headerExists) {
var token = req.headers.authorization.split(' ')[1];
jwt.verify(token, 's3cr3t', function(error, decoded) {
if (error) {
console.log(error);
console.log("Token 2" + token);
res.status(401).json('Unauthorized');
}
else {
req.user = decoded.username;
console.log("Here");
next();
}
});} else { console.log("Token 3" + token) res.status(403).json('No token provided'); } };
On checking with Postman this works fine. I am just not sure how to make it work on browser. Like it should show me a page when I hit "http://localhost:3000/users/showaddress" when logged in.
Can anyone please help with this.
Quick answer is, send the JWT via a cookie instead of a header, and the browser will do the job for you.
Note that this is only possible if you only have web clients. To support native clients too, you basically need to either airways send the JWT in a header, or let the client specify whether it wants it in a header or cookie. Native clients doesn't handle cookies that well.
If you want to stick with the header, you need a network layer in your client (JavaScript) application that adds the JWT as a header to each outgoing request to the server that issued the JWT. (Make sure to store the JWT in a secure storage in the client. Using cookies, you get this for free too.)
You probably med this layer anyway depending on how the backend behaves when a JWT expires. A common setup is that the client needs to detect when the JWT is expired (backend typically responds 401) and (try to) login again in the background.