Node, Express REST authenticate with JWT token - node.js

I am creating a blog using NodeJS and Express framework. To add extra security, I have separated the Sign Up & Login from the application. For this, I have created a REST API using Express in PORT 5000 (just for authentication of protected routes) and the actual application runs on 5001.
When user POSTS from the Login page in browser to ”http://www.example.com:5001” i.e., to my application. I authenticate the values from the REST API running on PORT 5000.
//First user posts login data into my application
router.post('/login', async (req, res) => {
try{
let payload = {
'username': req.body.username,
'password': req.body.password,
}
//sending the parameters for authentication to PORT 5000 (i.e., REST API)
const response = await axios.post('http://www.example.com:5000/login', payload);
if(response)
{
console.log(response.data);
newToken = {
token: response.data.token,
refreshToken: response.data.refreshToken
}
res.redirect('/');
}
}
catch(err){
console.log(err);
}
})
The REST API responds after receiving the request.
app.post('/login', async (req, res, next) => {
try{
let token, refreshToken;
await connectDB();
let result = await findOneUser(req);
if(result)
{
//Initialize JWT Token Signature
try {
//Creating jwt token
token = jwt.sign(
{ username: req.body.username },
"secretkeyappearshere",
{ expiresIn: "1h" }
);
refreshToken = jwt.sign({ username: req.body.username },
"some-secret-refresh-token-shit",
{ expiresIn: "3h" }
);
} catch (err) {
console.log(err);
const error = new Error("Error! Something went wrong.");
return next(error);
}
}
const response = {
"status": "Logged in",
"token": token,
"refreshToken": refreshToken,
}
res.send(response);
return next(false);
}
catch(err){
console.log(err);
tokenList[refreshToken] = response
res.status(200).json(response);
next(false);
}
});
Everything goes fine and I receive the token in my application.
But, problem starts when I resend the token. Suppose I have to authenticate a route (say) the settings route. I want users should again authenticate. So, I send back the token which I received during my first call to the REST API again, but now I don't receive anything back from the API to my application.
The first piece of code above has a variable called "newtoken", which I have set as global in my application.js file. I store the token that I received in the first call in this variable. Now, I use this to send another request to authenticate for the settings page/ route.
So, I again send this token in my next call like this
router.get('/settings', async (req, res) => {
let data = {
params: {
from: "2022-03-12",
to: "2022-03-13"
},
headers: {
"X-Auth-Token": newToken,
"content-type": "application/json"
}
};
try{
const call = await axios.post('http://www.example.com:5000/validate', data);
if(call){
console.log(call.data);
res.render('settings', { name: "Some test data" });
}
}
catch(err)
{
console.log(err);
}
});
As, you can see I am resending the token data with headers. Actually, I have tried all others ways of sending, but nothings works. I do not get any response from my next call. ANy help would be highly appreciated. Thanks in advance.

Your router will handle many requests (from different users) in parallel, but your global variable newToken exists only once, this cannot work.
Your router could either send the token to the client (browser) in a session cookie, or use express-session and store the token in req.session.token:
// newToken = {
// token: response.data.token,
// refreshToken: response.data.refreshToken
// }
req.session.token = response.data.token;
req.session.refreshToken = response.data.refreshToken;

Related

Cookies save and work fine in Insomnia app, but they do not work in my react application

I have a login page and a register page. When I register a user from the frontend form, it creates a new user. But when I try to log in to the user, I get the 200 status and my data back, but I guess it doesn't set the cookie. When I try to go to a protected route that only a logged-in user can access, I get the error I made from the backend which is "You are unauthenticated". How do I send or set the cookie in the front end too? Everything really works fine in the Insomnia app, the cookies get set.
this is how I'm making the post-login request
const submit = async (e) => {
e.preventDefault();
const data = { username, password };
try {
await axios.post(path, data).then((res) => {
console.log(res);
});
} catch (err) {
setLoading(false);
setError(err.message);
}
this is the login controller in the server side.
const login = async (req, res) => {
try {
const oneUser = await Users.findOne({ username: req.body.username });
if (!oneUser) {
return res.status(403).json("No such user in the database");
}
const isPassword = bcryptjs.compare(req.body.password, oneUser.password);
if (!isPassword) {
return res.status(500).json(`${req.body.username} Password is incorrect`);
}
const token = jwt.sign(
{
id: oneUser._id,
isAdmin: oneUser.isAdmin,
},
process.env.jwt
);
const { password, ...others } = oneUser._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json({ ...others });
} catch (err) {
res.status(500).json(err);
}
};

JWT token cannot be set to header (Node & express.js)

I'm working on a Node backend that uses MongoDB as its database. When I should send my JWT token within the response header, I get an error that the headers cannot be set after they are sent to the client. Here is my POST request:
api.post("/account/create", async (req, res) => {
// Hash the password using bcrypt
const hashedPassword = await bcrypt.hash(req.body.password, 10);
// Store new login credentials
const user = {
username: req.body.username,
password: hashedPassword
};
// Create a new JWT token
const token = jwt.sign({ username: req.body.username }, secret);
// Search for a matching username from the database
await logincollection.findOne(user, (err, result) => {
// If username was not found, add the credentials to the database
if (result == null) {
// Insert the credentials to the login credentials collection
logincollection.insertOne(user, (err, result) => {
// If an error occurred, return code 404 to the client
if (err) {
res.status(404).send();
}
})
// Create personal collection for the user
userdb.createCollection(JSON.stringify(user.username), (err, result) => {
// If an error occurred, return code 404 to the client
if (err) {
res.status(404).send();
}
})
// Return code 200 (success)
res.status(200).send({ auth: true, token: token });
} else {
// If username was found, return code 400 to the client
res.status(400).send();
}
})
})
When I try to get the token value in another POST request, it returns undefined:
api.post("/account/login", async (req, res) => {
// User object
const user = {
username: req.body.username,
password: req.body.password
};
const token = req.headers["token"];
console.log(token);
// Get username as a string
const username = JSON.stringify(user.username);
// Get hashed password from the collection
const hashedPassword = await logincollection.findOne({ username: req.body.username });
console.log(hashedPassword);
// Search for matching login credentials
await logincollection.find(user, (err, result) => {
// If no token was given
if (!token) {
// Return code 401 to the client
res.status(401).send();
}
// Verify the given JWT token
jwt.verify(token, secret, (err, decoded) => {
// If verification failed
if (err) {
// Return code 500 to the client
res.status(500).send();
}
// Return code 200 and decoded token to the client
res.status(200).send(decoded);
})
// Use bcrypt to compare the passwords and authenticate login
bcrypt.compare(req.body.password, hashedPassword).then(match => {
// If the credentials match
if (match) {
// Return the result as an object
const sendObject = {
username: result.username,
password: result.password
};
// Return code 200 to the client
res.status(200).send(sendObject);
// Log to console when user logs in
console.log("User " + username + " logged in");
// If the credentials do not match
} else {
// Return code 404 to the client
res.status(404).send();
}
// If comparing fails
}).catch(error => {
// Return coe 500 to the client
res.status(500).send();
})
})
})
I'm pretty sure the solution is something incredibly simple, but I just can't seem to solve this, although I've done a lot of research already.
Here's the response returned to the client from the /account/create request:
{
"auth": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QyIiwiaWF0IjoxNjMzNzg3MjE2fQ.duo5R9wXpk2Gj-iPHFMaDgKK0p3h6WZf5vnXrZViePo"
}
EDIT: Turns out that the token does not go the header, but to the body. What do I need to do differently to get it passed to the header?
Okay, it looks like I had understood this wrong.
I thought that the token has to be always returned and received in a header. This is not the case. The token response from /account/create has to be in the body. The token has to be set to the header only, when triggering the /account/login request (authenticating the login).
Hopefully this might help someone having the same question in the future.
In the result == null case, you have three statements that send a response: two asynchronous res.status(404) statements and one synchronous res.status(200) statement. Only of them may be executed per request.
But with the current code, the status 200 statement is executed for every request, even if a failed database operation later leads to an additional status 404 statement, which then causes the observed error.

JSON webtoken login authorisation for react and express protected routes

I am struggling to make a login system using JSON web tokens.
I have made the login (client side) that calls to my server.js file.
This is the login through the client side Below is my handle submit function that calls the server.js login route.How would I use a token here?
handleSubmit(e) {
e.preventDefault();
if (this.state.email.length < 8 || this.state.password.length < 8) {
alert(`please enter the form correctly `);
} else {
const data = { email: this.state.email, password: this.state.password };
fetch("/login", {
method: "POST", // or 'PUT'
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(data => {
console.log("Success:", data);
})
.catch(error => {
console.error("Error:", error);
});
}
}
catch(e) {
console.log(e);
}
This is the login route for my server.js. As you can see I have assigned a jwt but how would I send this back to my login form and utilise it for protected routes.
app.post("/login", async (req, response) => {
try {
await sql.connect(config);
var request = new sql.Request();
var Email = req.body.email;
var Password = req.body.password;
console.log({ Email, Password });
request.input("Email", sql.VarChar, Email);
request.input("Password", sql.VarChar, Password);
const result = await request.execute("dbo.LoginUser");
if (result.recordsets[0].length > 0) {
console.info("/login: login successful..");
console.log(req.body);
const token = jwt.sign({ user: Email }, "SECRET_KEY", {
expiresIn: 3600000
});
var decoded = jwt.verify(token, "SECRET_KEY");
console.log(decoded);
response.status(200).json({
ok: true,
user: Email,
token: token
});
console.log(token);
} else {
console.info("/login: bad creds");
response.status(400).send("Incorrect email and/or Password!");
}
} catch (err) {
console.log("Err: ", err);
response.status(500).send("Check api console.log for the error");
}
});
Essentially all I want is for my submit handler to be called for login. Server returns a jwt token which can then be used to verify other routes.
There are two ways to route:
Use React-Redux and react-router.
Save the fetched JWT token into localStorage and use to validate route within your routes component.
I would recommend in using React-Redux / React-router for protected routing.
Here is a video link to Build Real Web App with React by
Rem Zolotykh
This will help you.

How does app.get() work on the server file?

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.

passing jwt in header

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.

Resources