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.
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 have been following the tutorial on creating a test API application from this article. At the end of the article i see a mention its best to encrypt the jwt token for added security so i wen searching for a way to do that as well. I ran into this article and it gives examples of how to encrypt the jwt token with RSA private/public keys.
THIS is where im getting stuck. After i have successfully signed up using the /signup route, i can then use the /login route to get my token. So im assuming this is where i use my private key to encrypt the token before sending it back to the user?
**Made repo public for testing - you will need only to provide a mongoDB connection string in app.js
Im stuck at the encrypt/decrypt portion of the process, any help appreciated.
router.post("/login", async (req, res, next) => {
passport.authenticate("token", async (err, user, info) => {
try {
if (err || !user) {
const error = new Error("An Error occurred");
return next(error);
}
req.login(user, { session: false }, async error => {
if (error) return next(error);
//We don't want to store the sensitive information such as the
//user password in the token so we pick only the email and id
const body = { _id: user._id, email: user.email };
//Sign the JWT token and populate the payload with the user email and id
const token = jwt.sign({ user: body }, PRIV_KEY, { algorithm: 'RS256' });
//Send back the token to the user
return res.json({ token });
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
And then when making calls to the "secure" routes i need to decrypt the token and verify it against the public key?
router.get("/profile", (req, res, next) => {
//We'll just send back the user details and the token
jwt.verify(req.query.token, PUB_KEY, { algorithms: ['RS256'] }, function(err, decoded) {
if (err.name === "TokenExpiredError") {
console.log("Whoops, your token has expired!");
}
if (err.name === "JsonWebTokenError") {
console.log("That JWT is malformed!", err); <------ GET ERROR HERE
}
if (err === null) {
console.log("Your JWT was successfully validated!");
}
// Both should be the same
console.log(decoded);
res.json({
message: "You made it to the secure route",
user: req.user
});
});
});
I don’t have the time to reproduce this. Your login part seems correct. However, you should try to setup protected routes like this, copied and tailored to your needs from your first article:
Setting up middleware to handle jwt decryption, make sure to require it in your app.js or wherever you need to, if you set it up in a separate file. This can be used as a middleware later on in your controllers:
const JWTstrategy = require('passport-jwt').Strategy;
//We use this to extract the JWT sent by the user
const ExtractJWT = require('passport-jwt').ExtractJwt;
//This verifies that the token sent by the user is valid
passport.use(new JWTstrategy({
//secret we used to sign our JWT
secretOrKey : PUB_KEY,
algorithms: ['HS256'],
//we expect the user to send the token as a query parameter with the name 'token'
jwtFromRequest : ExtractJWT.fromUrlQueryParameter('token')
}, async (token, done) => {
try {
//Pass the user details to the next middleware
return done(null, token.user);
} catch (error) {
done(error);
}
}));
Setup protected route, note that you don’t need to manually call jwt.verify, middleware handles it and populates req.user:
const express = require('express');
const router = express.Router();
//Let's say the route below is very sensitive and we want only authorized users to have access
//Displays information tailored according to the logged in user
router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res, next) => {
//We'll just send back the user details and the token
res.json({
message : 'You made it to the secure route',
user : req.user,
token : req.query.token
})
});
module.exports = router;
**Update based on your comment:
I cloned your repo and it is working for me, although I changed some things:
I added
app.use(bodyParser.json()); to app.js so that I could send the request bodies as json - this is not necessary if you prefer urlencoded
the problem is that secureRoute that you export is another router and you try to use it as a controller in app.js:
...
const secureRoute = require('./routes/secure-routes');
...
app.use('/user', passport.authenticate('jwt', { session: false }), secureRoute);`
*note that it will be /user route, if you want /profile please change it in like app.use('/profile', ...)
so instead of
router.get("/profile", (req, res, next) => {
//We'll just send back the user details and the token
res.json({
message: "You made it to the secure route",
user: req.user,
token: req.query.secret_token
});
});
it should be just a controller function:
...
module.exports = (req, res, next) => {
//We'll just send back the user details and the token
res.json({
message: 'You made it to the secure route',
user: req.user,
token: req.query.token // note that you don't use secret_token but token as a name
});
};
the third thing is to not forget to add token to the query params, when you call the API, like http://localhost:3000/user?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjVlODc2Yjc1YTVlNTk3MTRlOGFjMmI4NyIsImVtYWlsIjoiYUBiLmNvbSJ9LCJpYXQiOjE1ODU5MzMyNjR9.lcLuQeCMRy7Ef9zNkIt_rn4S22t2cm7YLRE7Jgp1Mpw
you should get the response:
{
"message": "You made it to the secure route",
"user": {
"_id": "5e876b75a5e59714e8ac2b87",
"email": "a#b.com"
}
}
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.
After the user has logged in and generated a token, I want to send it automatically in the header or something similar.
So far I managed to generate the token, and check if it exists and if it's valid, it seems to work fine as long as I copy paste it the url as "?token = generated token".
I wasn't able to understand how to send it without writing it myself in the URL of Postman.
I'm using these modules:
Express
Body-parser
Mongoose
JsonWebToken
So I'm curious if it's ok that I choose to generate the token only at the login of if I need to add it in the user's Schema.
I don't want to use Passport for now because I want to understand the basics first.
After searching for a while (the jwt documentation included) I didn't really managed to find something that I can understand and implement.
So here I am, if someone could guide me in the right direction, that'd be great.
Sorry for the bad indentation and thanks in advance.
Here is some code:
jwt-middleware.js
var jwt = require('jsonwebtoken');
var secret = 'mySecret';
module.exports = function (req, res, next) {
var token = req.body.token || req.headers['x-access-token'] || req.query.token;
if(!token) {
return res.status(404).send({error:'No token found'});
} else {
jwt.verify(token, secret, function(err, decode) {
if(err) {
return res.status(500).send({error:'Invalid token'});
} else {
// req.decode = decode;
decode = jwt.decode(token, {complete:true});
//console.log(req.headers);
// req.headers.authorization = token;
// console.log(req.headers.authorization);
console.log(decode.header);
console.log(decode.payload);
next();
}
});
}
}
routes/user.js
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
var verifyToken = require('../config/jwt-middleware');
var secret = 'mySecret';
//Import models
var User = require('../models/users');
router.get('/', verifyToken, function (req, res) {
User.find({}, function (err, storedUsers) {
if (err) {
return res.status(500).send({ error: 'No users found' });
} else {
return res.status(200).send(storedUsers);
}
});
});
router.post('/login', function (req, res) {
User.find().lean().exec(function (err, doc) {
for (var i = 0; i < doc.length; i++) {
if (req.body.username == doc[i].username && req.body.password == doc[i].password) {
var token = jwt.sign({username:req.body.username}, secret, {expiresIn:'1h'});
return res.status(200).send('You\'re now logged in ' + 'with the username: ' + doc[i].username + ' and password: ' + doc[i].password + ' \nJSON token: \n' + token);
}
}
return res.status(404).send({ error: 'Invalid username or password: ' + req.body.username });
});
});
Some screenshots:
No token
Login
Manually inserted token
OK, so I'll try and answer your question even though I'm not 100% sure I understand what you're asking. The basic flow of a JWT is that the user logs in, and you issue it. You don't store it because the whole point of a JWT is that there's no overhead on the server for storing it (allowing for a more distributed approach to user management). The exception is if you want to do a logout feature, but that doesn't look like it's one of your requirements.
From the standpoint of responsibilities, you should have a Login function or module which is responsible for verifying a user's credentials and issuing a token. You should have a Verification function or module that validates the token and places the decoded token on the request object for later use (no need to repeatedly decode). And you may (or may not) have an Authorization module that validates that a given user is allowed to perform a given task.
So, from the top. Note that you can let the DB do the query work rather than doing your own loop. I'm also assuming that your User schema will include a verifyPassword method that takes care of comparing salted and hashed passwords.
// login
router.post('/login', function (req, res, next) {
// note I didn't use lean() because I want access to User methods. Performance is less of an issue in my version, b/c the DB is only retrieving one user at most.
User.findOne({ username: req.body.username }).exec(function (err, user) {
if(err) return next(err);
if(!user) return res.status(401).send();
if (user.verifyPassword(req.body.password)) {
// maybe add more info about the user, like display name
var token = jwt.sign({username:user.username}, secret, {expiresIn:'1h'});
return res.status(200).send({message: 'You are now signed in', token: token});
}
}
return res.status(404).send({ error: 'Invalid username or password: ' + req.body.username });
});
});
Now the client will have access to the token more easily, and can send it on further requests.
// verify
module.exports = function (req, res, next) {
// this is fine, though I think the standard is that the token should be sent in the Authorization header with the format Bearer {token}
var token = req.body.token || req.headers['x-access-token'] || req.query.token;
if(!token) {
return next(); // this middleware just is responsible for decoding, other middleware checks authorization
} else {
jwt.verify(token, secret, function(err, decode) {
if(err) {
return next(); // again, a bad token doesn't necessarily mean anything for some application pages, put the logic elsewhere.
} else {
req.user = decode; // this will be { username } based on above
req.token = token; // generally you don't need it but just in case.
next();
}
});
}
}
Ok, so now further middleware will include a req.user that you can use to check if a given user should be allowed to see a resource or not. For example:
function userRequired(req, res, next) {
if (!req.user) return res.status(401).send({message: 'You must be logged in to view this page' });
return next();
}
This scales well to other checks, you could have one for various roles, etc.
My server has a registration api that provides a token after registration, and a middleware that authenticates a user's token. I need to register an account to get the token to do something else with my server. However, the middleware blocks my network request because I don't have a token yet.
So how can I create my account and token in this case? Get pass the middleware with some tricks?
Middleware:
// Middleware to verify token, it will be called everytime a request is sent to API
api.use((req, res, next)=> {
var token = req.headers.token
if (token) {
jwt.verify(token, secret, (err, decoded)=> {
if (err) {
res.status(403).send({ success: false, message: "Failed to authenticate user." })
} else {
req.decoded = decoded
next()
}
})
} else {
res.status(403).send({ success: false, message: "No Token Provided." })
}
})
Signin:
// Sign In with email API
api.post('/signInWithEmail', (req, res)=> {
User.findOne({
email: req.body.email
}).select(userFields).exec((err, user)=> {
if(err) {
throw err
}
if (!user) {
res.send({ message: "User doesn't exist"});
} else if (user) {
var validPassword = user.comparePassword(req.body.password);
if (!validPassword) {
res.send({ message: "Invalid Password"});
} else {
var token = createToken(user);
res.json({
success: true,
message: "Login Successfully!",
token: token
})
}
}
})
})
Make a function to check tokens and expose your routes such that whenever you need to call an authenticated route then you'll be checking the token first and then you'll expose the route.
Sample Code
Let's say this is my check token function
function checkToken(req, res, next) {
var x = req.token; //This is just an example, please send token via header
if (x === token)
{
next();
}
else
{
res.redirect(/unauthorized); //here do whatever you want to do
}
}
Now let's use the function for routes.
app.post('/protectedroute', checkToken, routename.functionname);
app.post('/notprotected', routename.functionname);
It's your call if you'd like to have separate routes for different codes or else you can just call specific code block via keeping them in function etc. on the main file i.e. app.js or server.js, whatever you have chosen.
What actually we are doing here is - we are making a middleware of our own to expose our routes through a channel of code blocks or functions.