I am making API Server with Node.js and Express.
Also I used JWT token authentication for auth user.
If token is expired, my scenario is here.
(Backend) Middleware detect expired
(Frontend) Receive token is expired
(Fronend) Refresh token request to backend
(Backend) Verify token is valid and if it expired, sign new token(with old token's payload) and response it to frontend
at number 4, my code is here.
try {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, SECRET, (err, decoded) => {
if(err.name === 'TokenExpiredError') {
const payload = jwt.verify(token, SECRET);
const userid = payload.userid;
const is_admin = payload.is_admin;
const refreshToken = jwt.sign({
userid: userid,
is_admin: is_admin
}, SECRET, {
algorithm: 'HS256',
expiresIn: '10m'
})
res.status(200).json({status: true, token: refreshToken});
}
else if(err) {
res.status(401).json({status: false, result: "Invalid token"});
}
})
} catch(e) {
//console.log(e);
res.status(401).json({status: false, result: "Token does not exist"});
}
After run it, throw errors line of const payload = jwt.verify(token, SECRET);.
Because if token is expired, it throws TokenExpiredError error.
I want to decode token and extract payload of expired token.
But in verify(), there is no information about payload.
So I read document, found some interest method decode().
But it mention that do not use decode(), because it doesn't check signature is correct or not.
Is there any solution about extract payload of expired token?
Thanks.
You can set the option ignoreExpiration to true to avoid getting this error for expired tokens (at that point you know it already) and then get the payload:
if(err.name === 'TokenExpiredError') {
const payload = jwt.verify(token, SECRET, {ignoreExpiration: true} );
// your code
}
Now you can be sure the token is valid but just expired.
Related
I want to destroy the JWT whenever user sends the logout request to the app.
First of all, I am not storing JWT in the database so I can not delete that and I am also not using cookies or sessions. Is JWT stored on the client side? If so how can I destroy the JWT and invalidate the user's requests after logging out of the app.
The token middleware:
module.exports = middlewares = {
authenticateToken: async (req, res, next) => {
try {
if (!req.headers["x-access-token"]) {
return res.status(401).json({
error: "Key x-access-token not found",
});
}
if (req.headers["x-access-token"] === "") {
return res.status(401).json({
error: "Token not found",
});
}
const token = req.headers["x-access-token"];
const data = jwt.verify(token, keys.JWToken);
if (!data) return res.status(401).json({ error: "Invalid token" });
req.data = data;
next();
} catch (err) {
return res.status(400).json({ error: err.message });
}
},
};
Here's how I generate token on the registration and login requests:
const payload = { id: new_user._id };
const JWToken = jwt.sign(payload, keys.JWToken, { expiresIn: 31556926 });
The code provided is from the server, hence I don't know how its being saved on the client side, usually it is done using localhost, cookie or session. But you have mentioned that you are not using cookie or session, hence there is a chance that you are using local storage to store the jwt token. You can check your local storage on chrome by going to developer options -> Application -> Local Storage. You may find your token by how you named it, you can access it and delete by localStorage.removeItem("name of your token");
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 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.
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!",
});
}
};