For context, I'm trying to send a one time link to the user's email as a reset password link that will take them to the reset password page if the jwt token is successfully verified. I followed a tutorial and created a dummy version where user info was stored locally and it worked perfectly. But when I try to implement into my main project which pulls user data from mySQL I keep getting a malformed error, I am checking all the values and everything matches including checking the token on the jwt website to see if it return the correct info which it does so I'm very confused as to what I've done wrong. The only thing that changes between the test and main project is where the data is pulled from. Here is my code for this part of the project:
// Create and send link
router.post('/forgot-password', (req, res, next) => {
var email = req.body.email
db.query('SELECT * FROM users_test WHERE email = ?', [ email ], (error, results) => {
if (results.length < 1) {
res.send('no user')
return
}
const user = results[0]
const secret = process.env.JWT_SECRET + user.password
const payload = {
email: email,
id: user.id
}
const token = jwt.sign(payload, secret)
const link = `http://localhost:5000/auth/reset-password/${user.id}/${token}`
console.log(link)
res.send('sent')
})
})
// verify token and display password reset page
router.get('/reset-password/:id/:token')= (req, res, next) => {
const { id, token } = req.params
db.query('SELECT * FROM users_test WHERE id = ?', [ id ], (error, results) => {
if (error) {
console.log(error)
}
const user = results[0]
const secret = process.env.JWT_SECRET + user.password
res.json({secret})
try {
var payload = jwt.verify(token, secret)
res.render('reset-password.hbs')
}
catch (e) {
console.log(e)
}
})
}
The line the error is point at: var payload = jwt.verify(token, secret)
The error I'm getting:
throw err; // Rethrow non-MySQL errors
^
JsonWebTokenError: jwt malformed
at Object.module.exports [as verify] (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\jsonwebtoken\verify.js:63:17)
at Query.<anonymous> (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\controllers\auth.js:497:29)
at Query.<anonymous> (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\Connection.js:526:10)
at Query._callback (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\Connection.js:488:16)
at Query.Sequence.end (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\protocol\sequences\Sequence.js:83:24)
at Query._handleFinalResultPacket (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\protocol\sequences\Query.js:149:8)
at Query.EofPacket (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\protocol\sequences\Query.js:133:8)
at Protocol._parsePacket (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\protocol\Protocol.js:291:23)
at Parser._parsePacket (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\protocol\Parser.js:433:10)
at Parser.write (C:\Users\winba\Desktop\SecureSoftware\Secure-Software-02\node_modules\mysql\lib\protocol\Parser.js:43:10)
Any help or ideas as to where the error is coming from would be appreciated, thank you.
Try with following changes
const link = `http://localhost:5000/auth/reset-password/${user.id}/${JSON.stringify(token)}`
var payload = jwt.verify(JSON.parse(token), secret)
Related
I've been trying to generate a token to reset a user's password and send the user an email with the link to use that token.
This is the function forgotPassword defined in the class authController:
//forgot password => /api/v1/password/forgot
exports.forgotPassword = catchAsynErrors(async(req, res, next) => {
const user = await User.findOne({ email: req.body.email });
if(!user){
return next(new ErrorHandler('User not found with this email', 404));
}
//get reset token
const resetToken = user.getResetPasswordToken();
await user.save({validateBeforeSave: false});
// create reset password url
const resetUrl = `${req.protocol}://${req.get('host')}/api/v1/password/reset/${resetToken}`;
const message = `Your password reset token:\n\n${resetUrl}\n\n If you have not requested this email, please ignore it.`
try {
await sendEmail({
email: user.email,
subject: 'ShopIt password recovery',
message
})
res.status(200).json({
success: true,
message: `Email sent to: ${user.email}`
})
} catch (error) {
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
await user.save({validateBeforeSave: false});
return next(new ErrorHandler(error.message, 500))
}
})
Then, the function getResetPasswordToken defined in the model class user:
// generate password reset token
userSchema.methods.getResetPasswordToken = function() {
//generate token
const resetToken = crypto.randomBytes(20).toString('hex');
//hash and set to resetPasswordToken
this.resetPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex')
// set token expiration date
this.resetPasswordExpire = Date.now() + 30 * 60 * 1000
return resetToken
}
Upon sending a POST request via Postman to the URL http://localhost:4000/api/v1/password/forgot with the following JSON body,
{
"email": "example#email.com"
}
I get this response:
{
"success": false,
error": {
"statusCode": 500
},
"errMessage": "getaddrinfo ENOTFOUND smpt.mailtrap.io",
"stack": "Error: getaddrinfo ENOTFOUND smpt.mailtrap.io\n at D:\\pruebas de programaciĆ³n\\Proyectazo\\backend\\controllers\\authController.js:93:21\n at processTicksAndRejections (node:internal/process/task_queues:96:5)"
}
I have seen suggestions that this could be a DNS error, but I have already tried manually setting up an IPv4 address like 192.168.1.45 and my DNS servers set to 1.1.1.1 and 1.0.0.1, and the internet worked normally but I kept getting the same error, so I set my network configuration back to DHCP. I also tried emailing both real and fake email addresses and it made no difference.
Any ideas?
I am using a query getUsers to fetch the user by passing the token to header
let user
getUsers: async (_,__,context) => {
if(context.req && context.req.headers.authorization){
const token = context.req.headers.authorization.split("Bearer ")[1]
jwt.verify(token, JWT_SECRET, (err, decodedToken) =>{
if(err){
throw new AuthenticationError("Unauthenticated")
}
user = decodedToken
console.log(user)
})
}
But this is throwing the error "Unauthentication", When I tried to console.log(decodedToken). It was showing undefined. How can I overcome this?
I have a question on how to search for my JWT token inside of a user's browser cookies.
Below I have some code that searches the user's browser for cookies in the response header, but I am not sure how to make the code more specific and search for the JWT token within the cookie and verify that it is an actual JWT token that was a assigned to that user.
const jwt = require('jsonwebtoken');
const router = require('express')();
const cookieParser = require('cookie-parser');
router.use(cookieParser());
module.exports = function(req,res,next){
const token = req.header('Cookie');
if (!token) {
return res.status(403).send('Access Denied');
}
try{
const verified = req.header('Cookie');
req.user = verified;
// const verified = jwt.verify(token, process.env.TOKEN_SECRET);
// req.user = verified;
next();
} catch (err) {
res.clearHeader;
res.status(403).send('Invalid Token');
}
};
I hope I didn't misunderstand your question and waste a bunch time.
Short Answer: How to retrieve information
Use req.body or req.headers. If something will contain the token or authentication details, then it's one of these two.
Full Auth Walkthrough:
To get the JSON Web Tokens you first have to generate them. Wouldn't recommend implementing your own token authentication though. I'll show how to create a whole authentication system here step by step.
For simplicity, let's say we have an exported route in a file auth.js, this route will be a sub-route domain.com/auth, an array of all active refreshTokens and the jwt:
const express = require("express")
const jwt = require("jsonwebtoken")
let route = (exports.route = express())
let refreshTokens = []
What we will do is generate a long-lasting refresh token, which users will be able to use to generate a smaller 15-minute access token. Afterwards, you generate a new access token with the refresh token and so on. But to get the refresh token you need to login or register. Users can also logout killing the refresh token.
route.post("/token", async (req, res) => {
// Input: Refresh Token
// Output: Access Token Generation
})
route.post("/login", async (req, res) => {
// Input: User, Password
// Output: Refresh Token
})
route.delete("/logout", async (req, res) => {
// Input: Token to Remove
})
Let's start with the end. You have a refresh token, you won't to destroy it. Simply filter the array against this token and submit a status. The token becomes unusable after it's cleared from the array, that's the goal here.
route.delete("/logout", async (req, res) => {
refreshTokens = refreshTokens.filter((token) => token != req.body.token)
res.sendStatus(204)
})
With me so far? Now let's jump back to the start. If you log in with an email and password, if they're wrong respond with an error message, if they're correct receive the tokens.
route.post("/login", async (req, res) => {
const username = req.body.username
const password = req.body.password
// This is just a quick demonstration,
// you would have to use the bcrypt hash
// or other hash/salt methods. DO NOT
// STORE passwords plaintext
// Not existent user = Unauthorized
if (username != 'admin') return res.sendStatus(401)
// Wrong Password = Forbidden
if (password != 'abc123') return res.sendStatus(403)
const user = {
id: 0,
username: username,
password: password
}
const accessToken = generateAccessToken(user)
const refreshToken = generateRefreshToken(user)
let result = {
success: true,
accessToken: accessToken,
refreshToken: refreshToken,
}
res.send(result)
})
Now how do we sign the JSON web tokens? Let's take a look at the two methods used here:
function generateAccessToken(content) {
return jwt.sign(content, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: "15m",
})
}
function generateRefreshToken(content) {
const token = jwt.sign(content, process.env.REFRESH_TOKEN_SECRET)
refreshTokens.push(token)
return token
}
Both use some sort of environment tokens, but why? That's the token you will have to generate once for the back end. It will be used as a public key. We simply generate the access tokens for 15 minutes and push the refresh tokens to the array.
route.post("/token", async (req, res) => {
const refreshToken = req.body.token
if (refreshToken == null) return res.sendStatus(401)
if (!refreshTokens.includes(refreshToken)) return res.sendStatus(403)
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403)
res.json({ accessToken:
generateAccessToken({
id: 0,
username: user.name,
password: user.password
})
})
})
})
We verify the refresh token, if it exists and it is valid, return a new access token for 15 minutes. That's it for the token part, you can login (create refresh token), retrieve an access token and logout (kill refresh token)
How to Use: Authenticate and Authorize
Admin pages should return 403 while the forum board should be different whether you're logging as a guest or an actual user. The first one is authentication, the second authorization.
Let's create two functions for each. Express is quite handy with the next() function
exports.authenticate = function (req, res, next) {
const authHeader = req.headers["authorization"]
const token = authHeader?.split(" ")[1]
jwt.verify(token || "", process.env.ACCESS_TOKEN_SECRET, (err, user) => {
req.user = err ? {} : user
next()
});
};
exports.authorize = function (req, res, next) {
const authHeader = req.headers["authorization"]
const token = authHeader?.split(" ")[1]
if (token == null)
return res.sendStatus(401)
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403)
req.user = user
next()
})
}
Now you're done with the whole authentication system (aside some cleanup's) and probably the registration system. Let's make use of it.
Client side you can create a REST api like so:
POST http://localhost:8081/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "abc123"
}
# Returns refresh and access token.
###
DELETE http://localhost:8081/auth/logout
Content-Type: application/json
{
"token": "REFRESH_TOKEN"
}
# Logs out a user.
###
POST http://localhost:8081/auth/token
Content-Type: application/json
{
"token": "REFRESH_TOKEN"
}
#
# This is how you can provide the access token
# when making a request to say a forum api
#
GET http://localhost:8081/forum/api/board/0
Authorization: Bearer ACCESS_TOKEN
Usage:
route.get("forum/board/:id", authenticate, async (req, res) => {
res.send(req.user)
})
Expected Output when going to localhost:8081/forum/board/7 authenticated:
{id:0,username:"admin",password:"abc123"}
Otherwise:
{}
Nevertheless, do not try implementing your own authentication. Really, you shouldn't.
Source
https://www.youtube.com/watch?v=mbsmsi7l3r4
Hi I have been struggling significantly with making a login using react front end, npm express, npm mssql and npm jsonwebtokens.
I understand the logic behind this but cannot implement properly into my application. I have included the code in which I am getting stuck on and which is causing the issues.
This is my login route within my server.js (main npm packages used are mssql, express, jsonwebtokens,bodyparser)
This route is used when the logging form is submit (front end not important within this question).
This then checks if the database contains that email and password. If it does it prompts user "login successful" if not it prompts user "bad creds". If login is successful it assign a jwt token to it.
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);
var queryString =
"SELECT * FROM TestLogin WHERE email = #Email AND password = #Password";
//"SELECT * FROM RegisteredUsers WHERE email = #Email AND Password = HASHBYTES('SHA2_512', #Password + 'skrrt')";
const result = await request.query(queryString);
if (result.recordsets[0].length > 0) {
console.info("/login: login successful..");
console.log(req.body);
jwt.sign({ Email }, "secretkey", { expiresIn: "30m" }, (err, token) =>
res.cookie("auth", token).json({ 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");
}
});
// Verify Token
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers["authorization"];
// Check if bearer is undefined
if (typeof bearerHeader !== "undefined") {
// Split at the space
const bearer = bearerHeader.split(" ");
// Get token from array
const bearerToken = bearer[1];
// verify the token and store it
jwt.verify(bearerToken, "secret", function(err, decodedToken) {
if (err) {
console.info("token did not work");
return res.status(403).send("Error");
}
// Set the token
req.token = bearerToken;
req.decodedToken = decodedToken;
next();
});
} else {
// Forbidden
res.sendStatus(403);
}
}
I then have a token verify method.
This is supposed to check the token and then if it is legit will store it. However when using route user-questions(will show in next block of code) it always throws the 403 error.
// Verify Token
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers["authorization"];
// Check if bearer is undefined
if (typeof bearerHeader !== "undefined") {
// Split at the space
const bearer = bearerHeader.split(" ");
// Get token from array
const bearerToken = bearer[1];
// verify the token and store it
jwt.verify(bearerToken, "secret", function(err, decodedToken) {
if (err) {
console.info("token did not work");
return res.status(403).send("Error");
}
// Set the token
req.token = bearerToken;
req.decodedToken = decodedToken;
next();
});
} else {
// Forbidden
res.sendStatus(403);
}
}
This is the user question route in which on the first line I am trying to call the verify Token method when this fetch is called however it always throws the 403 error.
app.get("/user-questions", verifyToken, function(req, res) {
// if a request has made it to this point, then we know they have a valid token
// and that token is available through either req.token (encoded)
// or req.decodedToken
sql.connect(config, function(err) {
if (err) console.log(err);
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.execute("dbo.ViewQuestions", function(err, recordset) {
if (err) console.log(err);
// send records as a response
res.json(recordset);
});
});
});
I have to implement security on my app by preventing users to access other users profile.
route.js
router.get('/currentUser/:username', userAuth, (req, res) => {
User.findOne({
username: req.params.username
}).then(user => {
if (user) {
return res.status(200).json(user);
} else {
return res.status(404).json({
message: 'User not found'
});
}
});
});
and my userAuth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, 'app_secret_token');
next();
} catch (error) {
res.status(401).json({
message: 'Authentication failed!'
});
}
};
now if I am logged in as user test so my URL will be http://localhost:4200/currentuser/test but if I change my URL to another user test2 it redirects and loads the test2 even though I am logged as test
how do I prevent this?
You need to also check that the logged in user accesses his data.
you can achieve this by checking the user in the token against the requested page. This means you need to encode the user Id inside the jwt token. That will also make sure this parameter wasn't meddled with since jwt.verify would fail if someone tried to change the jwt token without having the secret.
you can add that data to the jwt token when signing it:
jwt.sign({
userId: 'username'
}, 'secret', { expiresIn: '1h' });
Basically if you save the same data as serializeUser\deserializeUser result, it should also work (the username is just a suggestion).
you can use the callback from jwt.verify to get the decoded token and retrieve that data
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, 'app_secret_token', (err, decoded) => {
if (err) { throw err; }
const currentUsername = decoded.userId; // <-- this should be whatever data you encoded into the jwt token
// if the user requested is different than the user in the token,
// throw an authentication failure
if (req.originalUrl.includes('/currentUser/') &&
!req.originalUrl.includes(`/currentUser/${currentUsername}`)) {
throw new Error('access to other user data denied');
}
next();
});
} catch (error) {
res.status(401).json({
message: 'Authentication failed!'
});
}
};
Even though I think this might be a good case separating this into two different middlewares :-)
PS - as #anand-undavia mentioned, it might be better to identify the user request based on the jwt token itself instead of the 'url' itself. that way, each user should only have access to their own data and this problem can't occur at all.
basically, the user should be accessible with the method above (getting it from the token) or from a req.user field if you use any middleware that adds it automatically.
let us assume that user profile page id mydomian?passedId=userId so simply add profile-guard to check who can visit or activate this page, in CanActivate check if passed id id the same of current user id, then return true else redirect him to previous page
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
let passedId: string = next.queryParams.passedId;
let user = this.authService.GetUser();
if (user.id == passedId)
return true;
else {
this.router.navigate(['/home']); // or any page like un authorized to log to this page
return false;
}
}