Using JSON Web Tokens in Node.js

 

JWT is an acronym for JSON Web Token. They are little packages of data that can be stored on the client, and used to verify a valid session. In the old days, we used to use server side sessions as a way of ensuring the user making the request, was logged in and had an active session. This was great, but as the demand for faster web apps and more scalable web-services became higher, the community moved to stateless.

What this means, is that the server does not keep a state of the users session, rather gives a token to the user, that the server may check the validity of their session on every request. This does carry a few security risks, but is generally accepted as better practice. Now bare in mind, that using a JWT is only part of the equation. If you want to handle sensitive data, you should still use server-side sessions, and the JWT is only used as a validation for the ID of that session. This helps prevent things like CSRF and session hijacking.

Just a quick point, to be truly safe from CSRF, you should use a CSRF token. I’ll cover this in another article and put a link here when it’s live.

So the way I use them (and this may not line up with your use-case), is like this:

  1. User submits login from
  2. Compare passwords and if valid login, generate a token
    2.1 Token contains a list of permissions such as is_admin, is_member etc
  3. Return token to client and store on frontend
    3.1 Use cookies or localstorage
  4. Submit this token on every subsequent request
  5. Server validates JWT using signed secret and issuer

If you don’t yet understand how JWT’s work and how things like the payload is constructed, it’s signed and encrypted, I recommend a read of this article at medium.

So lets get into some code and see how this works in reality. We will start by installing the JWT library from NPM


    $ npm install --save jsonwebtoken

    public static generateToken(username:string, admin: boolean):string {
        let data = {
            sub: username,
            iss: 'https://yourdomain.com',
            admin: admin
        }
        let token:string = null;
        try {
            token = jwt.sign(data, 'abcdefghijklmnop', {
                expiresIn: '7d'
            });
        } catch(err) {
            console.log(err);
        }
        return token;
    }

Calling the generateToken method, we will pass in the username and admin value which will form part of the payload. We create out data object, where the sub is the user identifier, iss is the issuer, and admin meaning the user session has admin privileges. Next we attempt to sign the JWT inside of a try catch. The sign method of the JWT module takes three parameters, the payload, the signing secret and an options object with an optional callback.

The payload should be fairly self explanatory a this point. The secret should be a long string of random ascii characters that is saved as an environment variable. This allows us to push things to git without sharing the secret with anybody. The options object, I use only for the length of time in which the token expires. Though there are many more options you can pass https://github.com/auth0/node-jsonwebtoken

Assuming the try catch does fail, you should have a valid token you can now return to your client. We’re next going to take a token already retrieved from an express request and decode it to check it’s validity.


    public static loggedIn(req, res, next) {
        jwt.verify(req.cookies.authorization, secret, (err, decoded) => {
            if(err) {
                console.log('invalid user, redirecting to login page');
                res.redirect('/login');
            } else {
                decoded.loggedIn = true;
                req.decoded = decoded;
                req.admin = decoded.permissions === 1 ? true : false
                next();
            }
        });
    }
    }

    app.use('secure-page', Auth.loggedIn, (req, res) => {
        // user has passed validation and you can now render or respond with appropriate content.
    });

The middleware function, loggedIn, accepts the parameters req, res and next. We pull the token from the authorization cookie out of the request and pass it to the verify method of the JWT module. If the token is not valid, it will throw an error, and we redirect the request to the login route. If the token is successful, we create a variable loggedIn on the decoded packet to true for ease of use as well as the decoded payload itself and the administrator privileges onto the request.

Note, we can access this at any point later on by just pulling the payload from request.decoded…

Finally, we call the next callback which allows the request to move onto the next middleware function if there are any, or end up in our route where we can render a page / respond to an API request.

If anybody is wonder as I was when I first started learning about the JWT’s, there isn’t a way to invalidate a JWT. You can’t destroy it like you can destroy a session. You have a few options here.

  1. Delete the cookie from the client by setting it’s time to -1
  2. Blacklist the JWT from your server
  3. Change your secret (will invalidate ALL active JWT’s)

The first should be fairly obvious. The second would require you to keep a running array of blacklisted and check them on every request with a middleware function.

Bonus tip, if you want to secure all, or a section of your API / routes you can do something like this.


    app.all('*', Auth.loggedIn);
    app.all('/secure', Auth.loggedIn);