Search code examples
node.jsauthenticationmicroservices

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:

  1. User logs in with credentials
  2. Gets a token back which expires in 15 minutes
  3. A httpOnly cookie is set with the refresh token
  4. 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'
        });
    }
};

Solution

  • 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.