I have a simple program with a Auth and Todo-App like "Microservice".
I've implemented a basic auth flow:
httpOnly
cookie is set with the refresh token/todos
route passing the token as a req bodyClient 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.