I am new to node.js, express and JWT. I found similar questions here but they didn't help.
I am able to login and store the token in local storage but when I try set Authorization header for another request with the same token, it fails verification in the server. I checked the token both from the server and client, they are exactly the same but the verification is failing, please help !
Here is the code I am using to verify the token.
exports.verify = function(req, res, next) {
let accessToken = req.headers.authorization
if (!accessToken){
return res.status(403).send()
}
let payload
try{
// Never makes it through this
payload = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET)
next()
}
catch(e){
return res.status(401).json({success: false, message: "token expired or invalid"})
}
}
Here in app.js file I use the verify function like this for another route.
const { verify } = require('./controllers/auth')
const userRoutes = require('./routes/userRoutes')
app.use('/user', verify, userRoutes)
Where am I going wrong here ?
EDIT:
I added console.log(e) in the verify function, inside the catch() and got the the below result.
TokenExpiredError: jwt expired
at /home/shashank/Documents/sms/server/node_modules/jsonwebtoken/verify.js:152:21
at getSecret (/home/shashank/Documents/sms/server/node_modules/jsonwebtoken/verify.js:90:14)
at Object.module.exports [as verify] (/home/shashank/Documents/sms/server/node_modules/jsonwebtoken/verify.js:94:10)
at exports.verify (/home/shashank/Documents/sms/server/controllers/auth.js:62:23)
at Layer.handle [as handle_request] (/home/shashank/Documents/sms/server/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/home/shashank/Documents/sms/server/node_modules/express/lib/router/index.js:317:13)
at /home/shashank/Documents/sms/server/node_modules/express/lib/router/index.js:284:7
at Function.process_params (/home/shashank/Documents/sms/server/node_modules/express/lib/router/index.js:335:12)
at next (/home/shashank/Documents/sms/server/node_modules/express/lib/router/index.js:275:10)
at cookieParser (/home/shashank/Documents/sms/server/node_modules/cookie-parser/index.js:57:14)
{ expiredAt: 2020-10-29T17:30:31.000Z }
Let me show my .env file where I store the secret key info
ACCESS_TOKEN_SECRET=swsh23hjddnns
ACCESS_TOKEN_LIFE=3600
REFRESH_TOKEN_SECRET=dhw782wujnd99ahmmakhanjkajikhiwn2n
REFRESH_TOKEN_LIFE=86400
So, the access token has to last an hour right ?
The token is created like below
const jwt = require('jsonwebtoken')
// Not using a database right now.
let users = {
email: 'myemail#gmail.com',
password: 'password'
}
exports.login = function(req, res) {
let email = req.body.email
let password = req.body.password
// Simple validation !
if (!email || !password || users.email !== email || users.password !== password){
return res.status(401).json({success: false, message: "Incorrect username or password"})
}
//use the payload to store information about the user such as username, user role, etc.
let payload = {email: email}
//create the access token with the shorter lifespan
let accessToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
})
//create the refresh token with the longer lifespan
let refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.REFRESH_TOKEN_LIFE
})
//store the refresh token in the user array
users.refreshToken = refreshToken
//send the access token to the client inside a cookie
// res.cookie("jwt", accessToken, { httpOnly: true}) //secure: false, use this along with httpOnly: true in production
// res.setHeader('Authorization', accessToken);
res.json({
accessToken: accessToken,
success: true, message: "Authentication success"
});
res.send()
}
It looks like the problem you are experiencing is because of how you are storing the time out period.
From the Documentation for node-jsonwebtoken
expiresIn: expressed in seconds or a string describing a time span
zeit/ms. Eg: 60, "2 days", "10h", "7d". A numeric value is interpreted
as a seconds count. If you use a string be sure you provide the time
units (days, hours, etc), otherwise milliseconds unit is used by
default ("120" is equal to "120ms").
Because you are storing in your process.env, it looks like it is translating it to a string, instead of maintaining the integer value.
Test Code:
const jwt = require('jsonwebtoken')
require('dotenv').config();
let payload = {email: 'email'}
let accessToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
algorithm: "HS256",
expiresIn: process.env.ACCESS_TOKEN_LIFE
})
console.log(accessToken);
console.log('waiting 4 seconds');
setTimeout(function() {
let val = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
console.log(val);
}, 4000);
With the following process.env values, it fails
ACCESS_TOKEN_SECRET=swsh23hjddnns
ACCESS_TOKEN_LIFE=3600
REFRESH_TOKEN_SECRET=dhw782wujnd99ahmmakhanjkajikhiwn2n
REFRESH_TOKEN_LIFE=86400
But if we change the ACCESS_TOKEN_LIFE to
ACCESS_TOKEN_LIFE=3600S
It succeeds
Without the time unit, any request that is delayed by more than 3.6 Seconds will error out.
Related
This is my code where I save the token into a cookie
const sendToken = (user, statusCode, res) => {
const token = user.getJWTToken();
//options for cookie
const options ={
expires: new Date(
Date.now + process.env.COOKIE_EXPIRE * 24 * 60 * 60 * 1000
),
httpOnly: true
};
res.status(statusCode).cookie('token', token, options).json({
success: true,
user,
token
});
};
module.exports = sendToken;
I check in postman and the cookie has been saved
But later on when I try to get it in this function:
exports.isAuthenticatedUser = catchAsyncErrors( async(req,res,next) => {
const { token } = req.cookie;
if(!token){
return next(new ErrorHandler("Please Login to Access this Resource.", 401));
}
const decodedData = JsonWebTokenError.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decodedData.id);
next();
});
It given the error Cannot destructure property 'token' of 'req.cookie' as it is undefined.
I'm new to Nodejs so I was following a tutorial. So I'm not sure what I'm doing wrong.
If you are using cookieParser (from the way you are setting the cookie), try changing this:
const { token } = req.cookie;
To this:
const { token } = req.cookies['token']
Make sure you use something like cookie-parser and that your user is logged in.
You can also see cookies in the postman cookie section. See below - if the cookie is not there, then you have to generate a cookie first.
I'm implementing an web app that contains a chatbot that will remind the user on his upcoming google calendar events. I have successfully generated a jwt token when the user authorizes, but, I'm getting this error "JsonWebTokenError: invalid signature" when I verify the token. I'm still new to these concepts so I would really appreciate any help.
Here is where I signed my token:
let iss = 'GoogleCalender'
let sub = 'example#gmail.com'
let aud = 'xxxxxxxxxxxxxx'
let exp = '24h'
let sighOptions = {
issuer: iss,
subject: sub,
audience: aud,
expiresIn: exp,
algorithm: "RS256"
}
app.get('/landingPage', (req, res) => {
const token = jwt.sign({ user: 'iman' }, privateKey , sighOptions);
res.cookie('token', token,{ httpOnly: true });
res.sendFile(path.join(__dirname, "./landingPage.html"));
});
And here is where I verify the token:
let verifyOptions = {
issuer: iss,
subject: sub,
audience: aud,
maxAge: exp,
algorithms: "RS256"
}
function verifyToken(req,res,next){
const baererHeader = req.headers['authorization']
if(typeof baererHeader !== 'undefined'){
const baerer = baererHeader.split(' ')
const baererToken = baerer[1]
req.token = baererToken
next()
}
else{
res.sendStatus(403)
}
}
app.post('/landingPage',verifyToken, express.json(),(req,res)=>{
token = req.token
jwt.verify(token, publicKey, verifyOptions, (err,authData) =>{
const calendar = google.calendar({version: 'v3' , auth:createConnection()});
const agent = new dfff.WebhookClient({
request : req,
response : res
})
if(err) {
console.log(err)
function welcome(agent){
agent.add("Hi, Im helen, Please log in so i can remind you on your upcoming events")
}
}
else{
function welcome(agent){
agent.add("Hi, I'm Rem. Please click on remind me button if you want to be reminded on your upcoming events!")
} )
});
Is there any thing I'm doing wrong??
It's good that you're using a pair of private and public keys. It's better to use asymmetric signing than symmetric.
In your code I can see that you're sending the JWT token in a httpOnly cookie, but then in the landingPage you read it from the Authorization header. Not sure how is that supposed to work. Are you sure you're sending the right JWT to the /landingPage endpoint?
If you want to use this JWT that you issued yourself to access a user's data in a Google Calendar then it will not work. To access this data you need an access token issued by Google. Have a look at Google's documentation to check how to obtain an access token from them which will allow you to call the calendar API. You can still use the token that you are creating as a way of protecting your own endpoints. So: the user will need your token to be able to call your endpoint, and then a token from Google will be used to call the calendar API.
i am new in node js. I am building a simple notes taking app and wanted to use JWT tokens for authentication and to secure my API's. On research i came to know that i need to create two tokens:
access token (short expire time like 10 minutes)
refresh token (longer expire time 30 days)
My config file
"secret": "*************",
"refreshTokenSecret": "*************",
"port": 5000,
"tokenLife": 900,
"refreshTokenLife": 86400
Code for middleware
const jwt = require('jsonwebtoken')
const config = require('./config')
module.exports = (req,res,next) => {
const token = req.body.token || req.query.token || req.headers['x-access-token']
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, function(err, decoded) {
if (err) {
return res.status(401).json({"error": true, "message": 'Unauthorized access.' });
}
req.decoded = decoded;
next();
});
} else {
// if there is no token
// return an error
return res.status(403).send({
"error": true,
"message": 'No token provided.'
});
}
}
Here is the response
access token can be saved in local storage. but articles said save refresh token as http-only cookie.
i need the answer of following points (Keeping in mind that i am just a beginner):
How to store refresh token as http-only cookie (any node-js code
example would be a great help)?
How to secure it on client side and should I save refresh token to database?
Is there any other better solution to secure my API's?
You can use an http-only cookie using the following:
public authenticateUser(user: User, res: Response) {
const authJwtToken = this.generateJWT({
email: user.email,
uuid: user.uuid
});
const cookieOptions = {
maxAge: 3600000,
secure: true,
httpOnly: true
};
res.cookie('access_token', authJwtToken, cookieOptions);
}
// you can then res.send({...}) or wtv
Not that there is nothing from preventing you to store more than one cookie so I can't see a reason why not storing both of them in the same manner.
Now whether you will store it on the database depends on what you want to achieve.
Generally it is not required but note that in that case the server cannot in any way invalidate a single JWT. (You could in theory change the signing key but this would invalidate all of them).
In case you want to be able to achieve functionality such as 'log me out of all devices' you would need to store the JWTs issued for each user in a database (preferably an in-memory one such as Redis or Memcached) and do a second check with the extra information on whether they have been invalidated or not - even though such functionality is typically achieved using sessions instead of JWT
See this example how i secured my getByRefId api in nodjs :
In routes file :
router.get("/refId/:refId", helper.auth, groupController.getByRefId);
helper.auth is function :
auth: (req, res, next) => {
var token = req.body.token || req.headers['authorization'] || req.headers['Authorization'];
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length).trimLeft();
}
if (token) {
jwt.verify(token, 'MY_SECRET', function (err, decoded) {
if (err) {
console.error('JWT Verification Error', err);
return res.status(403).send(err);
} else {
req.payload = decoded;
return next();
}
});
} else {
res.status(403).send('Token not provided');
}
}
This use jwt = require('jsonwebtoken') library you can install it in nodejs project
I'm having trouble with Jwt and especially an error "Invalid Signature".
I'm generating a token after the user logs in (jsonwebtoken).
userSchema.methods.generateJwt = function() {
var expiry = new Date();
//expiry.setDate(expiry.getDate() + 7);
expiry.setDate(expiry.getDate() + 2);
return jwt.sign({
_id: this._id,
username: this.username,
name: this.lastname,
exp: parseInt(expiry.getTime() / 1000),
}, process.env.SRCT, {
algorithm: 'HS256'
});
}
Then I'm creating an express-jwt middleware to add it to routes :
var auth = jwt({
secret: process.env.SRCT,
userProperty: 'payload'
});
Used like this :
router.get('/', auth, ctrlUser.slash);
My JWT created is passed in the front end request (Authorization bearer) and is the same as the one created right after the login, according to the debugger.
But unfortunatly, I'm still having the error {"message":"UnauthorizedError: invalid signature"} after each request to the nodejs backend.
Could someone tell me what I am doing wrong to have an invalid signature?
Thanks in advance
Where is your verify function ? You need to check on every request made to a protected area that token is really valid, jwt provides a function verify to do that.
You don't seem to be parsing the request headers for the token, nor using verify() function of the JWT library for that. your auth middleware should look something like this
module.exports = (req, res, next) => {
try {
//parse the token from Authorization header (value of "bearer <token>")
let token = req.headers.authorization.split(" ")[1];
//verify the token against your secret key to parse the payload
const tokenData = jwt.verify(token, process.env.JWT_SECRET_KEY);
//add the data to the request body if you wish
req.user = tokenData;
next();
} catch (err) {
res.status(401).json({
message: "Unauthorized access error!",
});
}
};
Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}