Passport-jwt token expiration - node.js

I am using passport-jwt to generate my tokens but I noticed that the tokens never expire, is there any way to invalidate a particular token according to a rule set for me, something like:
'use strict';
const passport = require('passport');
const passportJWT = require('passport-jwt');
const ExtractJwt = passportJWT.ExtractJwt;
const Strategy = passportJWT.Strategy;
const jwt = require('../jwt');
const cfg = jwt.authSecret();
const params = {
secretOrKey: cfg.jwtSecret,
jwtFromRequest: ExtractJwt.fromAuthHeader()
};
module.exports = () => {
const strategy = new Strategy(params, (payload, done) => {
//TODO: Create a custom validate strategy
done(null, payload);
});
passport.use(strategy);
return {
initialize: function() {
return passport.initialize();
},
authenticate: function() {
//TODO: Check if the token is in the expired list
return passport.authenticate('jwt', cfg.jwtSession);
}
};
};
or some strategy to invalidate some tokens

The standard for JWT is to include the expiry in the payload as "exp". If you do that, the passport-JWT module will respect it unless you explicitly tell it not to. Easier than implementing it yourself.
EDIT
Now with more code!
I typically use the npm module jsonwebtoken for actually creating/signing my tokens, which has an option for setting expiration using friendly time offsets in the exp element of the payload. It works like so:
const jwt = require('jsonwebtoken');
// in your login route
router.post('/login', (req, res) => {
// do whatever you do to handle authentication, then issue the token:
const token = jwt.sign(req.user, 's00perS3kritCode', { expiresIn: '30m' });
res.send({ token });
});
Your JWT Strategy can then look like what you have already, from what I see, and it will automatically respect the expiration time of 30 minutes that I set above (obviously , you can set other times).

You can use the following strategy to generate JWT-token with expiration limit of 1 hr.
let token = jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: JSON.stringify(user_object)
}, 'secret_key');
res.send({token : 'JWT '+token})

I created a document in the database that stores the generated tokens and added an expiration date, when the user makes the request check if the token is expired or no.
This is verify strategy that I used.
/* ----------------------------- Create a new Strategy -------------------------*/
const strategy = new Strategy(params, (payload, done) => {
const query = {
token: jwtSimple.encode(payload, credentials.jwtSecret),
expires: {$gt: new Date()}
};
TokenSchema.findOne(query, (err, result) => {
if (err) done(err, null);
if (!result) done(null, null);
done(null, payload);
});
});
passport.use(strategy);
/* -------------------------------------------------------------------------------*/
It's work for me.

Related

How to add refresh token?

I have the following code i would like to add refresh token. How can I add it?
I am also asking for suggestions on how to improve the quality of the authorization method.
Unfortunately, my attempts always ended with an error or the lack of a specific action.
This is my snippet of code:
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const urlencodedParser = bodyParser.urlencoded({ extended: false });
const mysql = require('mysql2');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const TOKEN_SECRET = process.env.TOKEN_SECRET;
const app = express();
app.use(express.json());
const pathPublic = path.join(__dirname, '../public');
app.use(express.static(pathPublic));
app.post('/login', urlencodedParser, (req, res) => {
res.get(req.body.username + req.body.password);
const users = {
username: req.body.username,
password: req.body.password
};
// Connect to MySql
const db = require('./dbconnect');
// Select user:
const post = [users.username, users.password]
const sql = 'SELECT * FROM users WHERE name = ? AND password = ?';
const query = db.query(sql, post, (err, results) => {
if (err) {
throw err;
} else if (results.length >= 1) {
console.log("Successful login!!");
const token = generateAccessToken({ username: users.username });
res.redirect(`/admin?token=${token}`);
} else if (results.length <= 0) {
console.log("Not find user");
}
})
})
function generateAccessToken(username) {
return jwt.sign(username, TOKEN_SECRET, { expiresIn: '600s' });
}
function authenticateToken(req, res, next) {
token = req.query.token;
if (token == null) return res.sendStatus(401);
jwt.verify(token, TOKEN_SECRET, (err, user) => {
if (err) console.log(err);
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.get('/admin', authenticateToken, (req, res) => {
res.send('admin panel');
});
There are multiple ways of implementing refresh token in Nodejs, one way that I use a lot is to generate a new JWT with more expiration time than the access token, for instance 30 minutes for the access and 48 hours for the refresh one, to differentiate those tokens I add an additional payload called type, the value that you put in that property is yours but it can be simple text as ACCESS and REFRESH, then I will modify the authentication middleware to validate token and verify that the type property be equal to ACCESS, if the value of the property is different I will send an unauthorized response, when the token access is invalid, I will use the refresh jwt to generate a new access one, for that I would create another middleware which extracts information from the token and verifys that the type be equal to REFRESH if everything is correct then I will generate a two new token one for access and another for refresh.
This is the basic implementation you can improving it in many ways, for instance to have a blacklist to allow only use the refresh token once, but this is the general idea.
An example can be found here.

Where to store access token and how to keep track of user (using JWT token in Http only cookie)

Trying to understand how to get and then save user in client (using JWT token in Http only cookie), so that I can do conditional rendering. What I'm having difficulty with is how to continously know if the user is logged in or not, without having to send a request to the server each time the user changes/refresh page. (Note: the problem is not how do I get the token in the Http only cookie, I know that this is done through withCredentials: true)
So my problem is how do you get/store the access token so that the client will not have to make a request to the server each time the user does something on the website. For example the Navbar should do conditional renderingen depending on if the user is logged in or not, then I don't want to do "ask the server if the user has a access token, then if not check if user has refresh token, then return a new access token if true else redirect to login page" every single time the user switches page.
Client:
UserContext.js
import { createContext } from "react";
export const UserContext = createContext(null);
App.js
const App = () => {
const [context, setContext] = useState(null);
return (
<div className="App">
<BrowserRouter>
<UserContext.Provider value={{ context, setContext }}>
<Navbar />
<Route path="/" exact component={LandingPage} />
<Route path="/sign-in" exact component={SignIn} />
<Route path="/sign-up" exact component={SignUp} />
<Route path="/profile" exact component={Profile} />
</UserContext.Provider>
</BrowserRouter>
</div>
);
};
export default App;
Profile.js
import { GetUser } from "../api/AuthenticateUser";
const Profile = () => {
const { context, setContext } = useContext(UserContext);
return (
<div>
{context}
<button onClick={() => GetUser()}>Change context</button>
</div>
);
};
export default Profile;
AuthenticateUser.js
import axios from "axios";
export const GetUser = () => {
try {
axios
.get("http://localhost:4000/get-user", {
withCredentials: true,
})
.then((response) => {
console.log(response);
});
} catch (e) {
console.log(`Axios request failed: ${e}`);
}
};
Server:
AuthenticateUser.js
const express = require("express");
const app = express();
require("dotenv").config();
const cors = require("cors");
const mysql = require("mysql");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
// hashing algorithm
const bcrypt = require("bcrypt");
const salt = 10;
// app objects instantiated on creation of the express server
app.use(
cors({
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
credentials: true,
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
const db = mysql.createPool({
host: "localhost",
user: "root",
password: "password",
database: "mysql_db",
});
//create access token
const createAccessToken = (user) => {
// create new JWT access token
const accessToken = jwt.sign(
{ id: user.id, email: user.email },
process.env.ACCESS_TOKEN_SECRET,
{
expiresIn: "1h",
}
);
return accessToken;
};
//create refresh token
const createRefreshToken = (user) => {
// create new JWT access token
const refreshToken = jwt.sign(
{ id: user.id, email: user.email },
process.env.REFRESH_TOKEN_SECRET,
{
expiresIn: "1m",
}
);
return refreshToken;
};
// verify if user has a valid token, when user wants to access resources
const authenticateAccessToken = (req, res, next) => {
//check if user has access token
const accessToken = req.cookies["access-token"];
// if access token does not exist
if (!accessToken) {
return res.sendStatus(401);
}
// check if access token is valid
// use verify function to check if token is valid
jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
return next();
});
};
app.post("/token", (req, res) => {
const refreshToken = req.cookies["refresh-token"];
// check if refresh token exist
if (!refreshToken) return res.sendStatus(401);
// verify refresh token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(401);
// check for refresh token in database and identify potential user
sqlFindUser = "SELECT * FROM user_db WHERE refresh_token = ?";
db.query(sqlFindUser, [refreshToken], (err, user) => {
// if no user found
if (user.length === 0) return res.sendStatus(401);
const accessToken = createAccessToken(user[0]);
res.cookie("access-token", accessToken, {
maxAge: 10000*60, //1h
httpOnly: true,
});
res.send(user[0]);
});
});
});
/**
* Log out functionality which deletes all cookies containing tokens and deletes refresh token from database
*/
app.delete("/logout", (req, res) => {
const refreshToken = req.cookies["refresh-token"];
// delete refresh token from database
const sqlRemoveRefreshToken =
"UPDATE user_db SET refresh_token = NULL WHERE refresh_token = ?";
db.query(sqlRemoveRefreshToken, [refreshToken], (err, result) => {
if (err) return res.sendStatus(401);
// delete all cookies
res.clearCookie("access-token");
res.clearCookie("refresh-token");
res.end();
});
});
// handle user sign up
app.post("/sign-up", (req, res) => {
//request information from frontend
const { first_name, last_name, email, password } = req.body;
// hash using bcrypt
bcrypt.hash(password, salt, (err, hash) => {
if (err) {
res.send({ err: err });
}
// insert into backend with hashed password
const sqlInsert =
"INSERT INTO user_db (first_name, last_name, email, password) VALUES (?,?,?,?)";
db.query(sqlInsert, [first_name, last_name, email, hash], (err, result) => {
res.send(err);
});
});
});
/*
* Handel user login
*/
app.post("/sign-in", (req, res) => {
const { email, password } = req.body;
sqlSelectAllUsers = "SELECT * FROM user_db WHERE email = ?";
db.query(sqlSelectAllUsers, [email], (err, user) => {
if (err) {
res.send({ err: err });
}
if (user && user.length > 0) {
// given the email check if the password is correct
bcrypt.compare(password, user[0].password, (err, compareUser) => {
if (compareUser) {
//req.session.email = user;
// create access token
const accessToken = createAccessToken(user[0]);
const refreshToken = createRefreshToken(user[0]);
// create cookie and store it in users browser
res.cookie("access-token", accessToken, {
maxAge: 10000*60, //1h
httpOnly: true,
});
res.cookie("refresh-token", refreshToken, {
maxAge: 2.63e9, // approx 1 month
httpOnly: true,
});
// update refresh token in database
const sqlUpdateToken =
"UPDATE user_db SET refresh_token = ? WHERE email = ?";
db.query(
sqlUpdateToken,
[refreshToken, user[0].email],
(err, result) => {
if (err) {
res.send(err);
}
res.sendStatus(200);
}
);
} else {
res.send({ message: "Wrong email or password" });
}
});
} else {
res.send({ message: "Wrong email or password" });
}
});
});
app.get("/get-user", (req, res) => {
const accessToken = req.cookies["acceess-token"];
const refreshToken = req.cookies["refresh-token"];
//if (!accessToken && !refreshToken) res.sendStatus(401);
// get user from database using refresh token
// check for refresh token in database and identify potential user
sqlFindUser = "SELECT * FROM user_db WHERE refresh_token = ?";
db.query(sqlFindUser, [refreshToken], (err, user) => {
console.log(user);
return res.json(user);
});
});
app.listen(4000, () => {
console.log("running on port 4000");
});
I began experimenting with useContext as you can see in the client code above. My initial idea was to use useEffect in the App component where I make a call to the function GetUser() which makes a request to "/get-user" which will user the refreshToken to find the user (don't know if it is bad practice to use refreshToken to find user in db, maybe I should store access token in db as well and use it to find user in db instead?) and then save things like id, first name, last name and email so that it may be displayed in the navbar or any other component if necessary.
However, I don't know if this is the right thing to do as I have heard a lot about using localStorge, memory or sessionStorage is better for keeping the JWT access token in, while you should keep the refresh token in the server and save it in the mySQL database I have created, only to be used once the user has lost their access token. How should I get access to my access token and how do I keep track of the user logged in? Do I really need to do a request to the server each time the user switches page or refresh page?
Also I have a question about when I should be calling "/token" in the server to create new access tokens. Should I always try to use the access token to do things that require authentication and if it for example returns null at some point then I make request to "/token" and after that repeat what the user was trying to do?
Do I really need to do a request to the server each time the user switches page or refresh page?
That is the safest way. If you want to keep with the current security best practices for SPAs, then using http-only, secure, same-site cookies is the best option. Refreshes won't happen that often on your page, so it shouldn't be a problem.
My initial idea was to use useEffect in the App component where I make a call to the function GetUser() which makes a request to "/get-user" which will user the refreshToken to find the user
What I would do is to first verify the access token, if it's valid then take the userId out of the access token (if you don't have it there you can easily add it as you're creating the tokens manually) and read the user data from the database. If the access token is invalid then return an error to the website and let the user use the refresh token to get a new access token. So I wouldn't mix responsibilities here - I wouldn't use refresh token to get information about the logged in user.
Also I have a question about when I should be calling "/token" in the server to create new access tokens. Should I always try to use the access token to do things that require authentication and if it for example returns null at some point then I make request to "/token" and after that repeat what the user was trying to do?
Yes, that's how it usually is implemented. You make a call with the access token to a protected endpoint. It would be best if the endpoint returned 401 response if the token is expired or invalid. Then your app knows that it should use the refresh token to get a new access token. Once you have a new access token you try to make the call to the protected endpoint again. If you don't manage to get a new access token (e.g. because the refresh token has expired), then you ask the user to log in again.

Microservices and refresh tokens NodeJS

I have a simple program with a Auth and Todo-App like "Microservice".
I've implemented a basic auth flow:
User logs in with credentials
Gets a token back which expires in 15 minutes
A httpOnly cookie is set with the refresh token
User can now call /todos route passing the token as a req body
Client App (WebBrowser) automatically calls /refresh_token, to renew the token and refresh_token
Do I also need to store refresh tokens in a Users db and validate them on each /refresh_token request? Would there be any more good security practices or improvements to the way I have implemented the auth flow?
AuthService
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const PORT = 3001;
const app = express();
app.use(cookieParser('secret3'));
const user = {
id: 1,
username: 'sam',
password: '123',
ref_token: '' //
};
//Generate token and refresh token
app.post('/login', (req, res) => {
const payload = {
id: user.id
};
const token = jwt.sign(payload, 'secret', { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, 'secret2', { expiresIn: '60d' });
//Save refresh token to users db row
//Set refresh token in httpOnly cookie
let options = {
maxAge: 1000 * 60 * 60 * 24 * 30, // would expire after 1month
httpOnly: true,
signed: true
};
res.cookie('rt', refreshToken, options);
res.json({
token: token,
message: 'Login successful'
});
});
//Generates a new jwt and a refresh token from prev refresh token
app.get('/refresh_token', (req, res) => {
//Get the refresh token from cookie
const { rt } = req.signedCookies;
if (rt == null) {
return res.json({
message: 'Missing rt cookie'
});
}
//Verify refresh token against users db here...
//New authtoken and refreshtoken
const payload = {
id: user.id
};
const token = jwt.sign(payload, 'secret', { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, 'secret2', { expiresIn: '60d' });
//Update new refreshToken in DB
//Set refresh token in httpOnly cookie
let options = {
maxAge: 1000 * 60 * 60 * 24 * 30, // would expire after 1month
httpOnly: true,
signed: true
};
res.cookie('rt', refreshToken, options);
res.json({
token: token,
message: 'New tokens generated'
});
});
app.listen(PORT, () => {
console.log(`Auth microservice running on ${PORT}`)
});
TodoService
const express = require('express');
const verifyToken = require('./verifyjwt.js');
const bodyParser = require('body-parser');
const PORT = 3002;
const app = express();
app.use(bodyParser());
const todos = [
{
id: 1,
belongsTo: 1,
content: 'Buy milk',
isDone: true
},
{
id: 346457,
belongsTo: 5436,
content: 'Clean your desktop',
isDone: false
}
];
//Return user todos (protected route)
app.get('/todos', verifyToken,(req, res) => {
//Find where req.decoded.id matches todos.belongsTo...
res.send(todos[0]);
});
app.listen(PORT, () => {
console.log(`Todo microservice running on port ${PORT}`);
});
verifyjwt.js
const jwt = require('jsonwebtoken');
module.exports = (req,res,next) => {
const token = req.body.token;
//Decode token
if (token) {
//Verify secret and exp
jwt.verify(token, 'secret', function(err, decoded) {
if (err) {
return res.status(401).json({"message": 'Unauthorized access'});
}
req.decoded = decoded;
next();
});
} else {
return res.status(403).send({
"message": 'No token provided'
});
}
};
What you've done mostly looks good. As you're using JWTs there is no need to look the user up in the database as only you know the JWT secret, so by verifying the JWT you can then use the decoded user id and know that they are the correct user.
The only thing you might consider using a database for is to invalidate unexpired tokens, e.g. if the user logs out which you could probably do simply by storing the last logout time in the database, then before issuing a new refresh token, check the last logout time agains the issue time of the previous token.
Only thing you need to change, which I'm sure is deliberate for debugging, is to use the req.decoded.id value for the user id and not the hardcoded one.
One other thing I noticed in your code is in your jwt.verify function you return your unauthorised message inside a callback, which I think will just get swallowed, causing the request to hang if the token is invalid.

Get user id from jsonwebtoken in node with passport

I use passport jwt strategy and here is my code snippet:
'use strict';
const config = require('../config');
const User = require('../models/user.model');
const passportJWT = require('passport-jwt');
const ExtractJwt = passportJWT.ExtractJwt;
const JwtStrategy = passportJWT.Strategy;
const jwtOptions = {
secretOrKey: config.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
};
const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => {
console.log(jwtPayload);
User.findById(jwtPayload.sub, (err, user) => {
if (err) return done(err, null);
if (user) {
return done(null, user)
} else {
return done(null, false)
}
})
});
exports.jwtOptions = jwtOptions;
exports.jwt = jwtStrategy;
here I'm setting the strategy and everything works correctly. Now I want to create endpoint to add article with UserId retrieved from the token (article has userId String in its schema).
How am I suppose to get userId from the token?
In new JwtStrategy it's already happening because I have payload which allow me to findById and therefore authenticate the call but should I somehow reuse this file or create new named for example getUserId?
The JWT claim that identifies the user presenting the JWT is sub. If understand your code correctly, jwtPayload.sub contains this claim.
If the value of the sub claim contains what you call the user ID, you are done.
If you want to find the user based on a different value, maybe one derived from the sub, you will have to tell us how this different value is related to the claims contained in the JWT.
While signing the JWT (creating the new token), if you would add the user info in the jwt payload something as below:
..
....
const payload = {
userId: user.id,
email: user.email
};
// CREATE A JWT TOKEN
jwt.sign(payload, secretForJWT, { expiresIn: expirationTimeForJWT },
(err, token) => {
....
..
Then you should be able to retrieve the userId field from the decrypted token as below:
const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => {
console.log(jwtPayload);
const userId = jwtPayload.userId; // YOU SHOULD GET THE USER ID FROM THE PAYLOAD
User.findById(jwtPayload.sub, (err, user) => {
if (err) return done(err, null);
if (user) {
return done(null, user)
} else {
return done(null, false)
}
})
});
exports.jwtOptions = jwtOptions;
exports.jwt = jwtStrategy;
This is my way, hope it's helpful:
const jwt = require('jsonwebtoken');
...
const jwt_payload = jwt.decode(yourJwtInString);
const id = jwt_payload._id;
...

Passport JWT authentication extract token

I am using express & jwt-simple to handle login/register & authenticated requests as a middleware api. I'm trying to create a .well-known endpoint so other api's can authenticate request based on token send in.
Here's my strategy:
module.exports = function() {
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
opts.secretOrKey = securityConfig.jwtSecret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// User.where('id', jwt_payload.id).fetch({withRelated: 'roles'})
console.log('jwt_payload', jwt_payload)
User.where('id', jwt_payload.id).fetch()
.then(user => user ? done(null, user) : done(null, false))
.catch(err => done(err, false));
}));
};
Here's my login route:
router.post('/login', function(req, res) {
const {username, password} = req.body;
Promise.coroutine(function* () {
const user = yield User.where('username', username).fetch();
if(user) {
const isValidPassword = yield user.validPassword(password);
if (isValidPassword) {
let expires = (Date.now() / 1000) + 60 * 30
let nbf = Date.now() / 1000
const validatedUser = user.omit('password');
// TODO: Verify that the encoding is legit..
// const token = jwt.encode(user.omit('password'), securityConfig.jwtSecret);
const token = jwt.encode({ nbf: nbf, exp: expires, id: validatedUser.id, orgId: validatedUser.orgId }, securityConfig.jwtSecret)
res.json({success: true, token: `JWT ${token}`, expires_in: expires});
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
} else {
res.status(401);
res.json({success: false, msg: 'Authentication failed'});
}
})().catch(err => console.log(err));
});
Here's my .well-known route:
router.get('/.well-known', jwtAuth, function(req, res) {
// TODO: look over res.req.user. Don't seem to be the way to get those parameters.
// We dont take those parameters from the decrypted JWT, we seem to grab it from the user in DB.
const { id, orgId } = res.req.user.attributes;
console.log("DEBUG: userId", id)
console.log("DEBUG: USER", res.req.user)
res.json({
success: true,
userId: id,
orgId
});
});
here's my jwtAuth() function:
const passport = require('passport');
module.exports = passport.authenticate('jwt', { session: false });
How would I actually get the token in the route function & decrypt it? All this does right now which works is that it authenticates if true however I need to be able to decrypt the token to send back the stored values. I'm not sure what res.req.user.attributes comes from, is this the token?
Take a look at passport-jwt and in your passport-config (or wherever you initialize passport) setup JWT Strategy:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwtAuth = (payload, done) => {
const user = //....find User in DB, fetch roles, additional data or whatever
// do whatever with decoded payload and call done
// if everything is OK, call
done(null, user);
//whatever you pass back as "user" object will be available in route handler as req.user
//if your user does not authenticate or anything call
done(null, false);
}
const apiJwtOptions: any = {};
apiJwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
apiJwtOptions.algorithms = [your.jwt.alg];
apiJwtOptions.secretOrKey = your.jwt.secret;
//apiJwtOptions.issuer = ???;
//apiJwtOptions.audience = ???;
passport.use('jwt-api', new JwtStrategy(apiJwtOptions, jwtAuth));
If you want just decoded token, call done(null, payload) in jwtAuth.
Then in your route files when you want to protect endpoints and have info about user, use as:
const router = express.Router();
router.use(passport.authenticate('jwt-api', {session: false}));
And in handler you should have req.user available. It is configurable to what property of req you store data from auth, req.user is just default.

Resources