Aris
Aris

Reputation: 3338

Using JWT tokens. Is there a better approach?

I'm using JWT tokens via nJWT package to authenticate my users to my Socket.io using socket.io-jwt package.

More or less, the code looks like this. User sends a POST reques to play/login via HTML form to generate a JWT token. Then, socket.io client initializes using that token.

/**
 * Create Express server.
 */
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const socketioJwt = require('socketio-jwt');

app.set('jwt.secret', secureRandom(256, {
    type: 'Buffer'
}));

app.post('/play/login', (req, res) => {
    // validate user's req.body.email and req.body.password

    const claims = {
      iss: "http://app.dev", // The URL of your service
      sub: "user-1", // The UID of the user in your system
      scope: "game"
    };

    const jwt = nJwt.create(claims, app.get("jwt.secret"));
    const token = jwt.compact();

    new Cookies(req,res).set('access_token', token, {
        httpOnly: true,
        secure: process.env.ENVIRONMENT === "production"
    });

    tokenUserRelations[token] = req.body.email;

    res.json({
         code: 200,
         token: token
     });  
});

/**
 * Add Socket IO auth middleware
 */
io.set('authorization', socketioJwt.authorize({
    secret: app.get("jwt.secret"),
    handshake: true
}));

io.sockets.on('connection', function (socket) {

    socket.on('chat message', function (req) {
        io.emit("chat message emit", {
            email: tokenUserRelations[socket.handshake.query.token],
            msg: req.msg
        });
    });

    socket.on('debug', function (req) {
        io.emit("debug emit", {
            playersOnline: Object.keys(tokenUserRelations).length
        });
    });

    socket.on('disconnect', function (req) {
        delete tokenUserRelations[socket.handshake.query.token];
    });
});
io.listen(app.get('socket.port'), () => {
    console.log('Started! Socket server listening on port %d in %s mode', app.get('socket.port'), app.get('env'));
});

Right now, it works properly, but in order to track emails from tokens, I had to do this:

tokenUserRelations[token] = req.body.email;

so I can relate which user the token points to.

I have a feeling that keeping token<->email relations in a global object is going to cause me headaches in the future, especially when tokens/cookies expires.

Is there any better way about this? I need to know which user that JWT token points to so I can do some business logic with them.

Thank you.

Upvotes: 1

Views: 451

Answers (3)

alex
alex

Reputation: 5573

Adding to the excellent answers above, it is also important that if you decide to store your jwt.secret in a file and pull that in when the code loads that you do not add that to your git repository (or whatever other VCS you are using). Make sure you include a path to 'jwt.secret' in your .gitignore file. Then when you are ready to deploy your production code you can then set that key as an environment variable as suggested. And you will have a record of that key in your local environment if you ever need to reset it.

Using JWTs is an excellent and convenient way of securing your api, but it is essential to follow best practice.

Upvotes: 1

Paul
Paul

Reputation: 36319

You don't show anything in your code about how tokenUserRelations is created or maintained, but as soon as I hear "global" a red flag goes up in my head.

The JWT standard includes the concept of embedding 'claims' in the token itself; you're already doing so with your claims constant. That data format is arbitrary and can be trusted by your app so long as the overall JWT gets validated. Note that you'll want to verify JWT on every request. So, stuffing email into that claims object is not just fine, it's what most folks do.

As a sidenote, you should be careful about how you're setting your 'jwt.secret' right now. What you have now will generate a new one every time the app starts up, which means that a) all your users will be logged out and have to re-login every time the app restarts, and b) you can't make use of multiple processes or multiple servers if you need to in the future.

Better to pull that from the environment (e.g. an env var) than to generate it on app start, unless you're just doing so for debugging purposes.

Upvotes: 1

DrakaSAN
DrakaSAN

Reputation: 7853

A token can contain information about anything you want, this information is encrypted along the token.

What you can do is encrypt a user id in the token, when you receive a request, decrypt the token (which is anyway done when you verify it), and use the user id as normal.

This way, if the token expire, the new token will have the same user id, and your code will not be impacted.

This is what I did in one of my web app, and it worked fine. However, I was using the official jwt module

Upvotes: 1

Related Questions