What I learned on my own | Implementing JWT Authentication on SailsJS (v1)
Tags: node, api, microservices, jwt
Published: 2019-08-02 05:38:22

**What this article will teach you:**
- How to apply JWT Authentication in your SailsJS *v1* backend.

**What this article will NOT teach you:**
- How to create a SailsJS application.
- How to understand the mechanisms behind these functions.

The reason why I won't go in-depth with the mechanisms of some functions and how they work in the background is because there's a whole lot of articles and documentations out there, written by people who are much much more qualified than I am.

**Who is this article intended for?** Anyone who just needs to implement some JWT Authentication into SailsJS because they have a deadline and have no time to do prolonged research.

If you've been developing with Sails, you're going to realize that you won't be able to get all the answers you want, mainly because of Sails' migration from `v0` to `v1`, their not-so-[laravel](https://laravel.com/docs/5.8)ish-documentation and the rather small community accompanying Sails.

Enough with the small talk. Let me have a smoke and I'll come back to the technicalities.

---
_Disclaimer: some of this code is inspired from [this repo](https://github.com/vonWolfehaus/sails-jwt-login) written by [Corey Birnbaum](https://github.com/vonWolfehaus). So props for this guy._

The way this is going to be implemented is as follows, in a general, non-sails-terms sentences: 
- HTTP Request (requires authentication) hits your backend
- The Request is intercepted by [middleware](https://stackoverflow.com/questions/2904854/what-is-middleware-exactly)
- Authentication Valid
-- Redirect to controller
- Authentication invalid
-- [401](https://httpstatuses.com/401)

Okay great, but how do we implement this in Sails?
For this, I'm going to assume you've already set up your [routes and actions](https://sailsjs.com/documentation/concepts/actions-and-controllers/routing-to-actions).

In simple terms, a _middleware_ in Sails is called a [policy](https://sailsjs.com/documentation/concepts/policies). Which means, you're going to assign to a controller/action a set of rules, which basically tells that controller/action "Hey! I know you're expecting this HTTP Request to be redirected to you, but first I need to make a few checks. If these checks pass, I'll send it over. If not, I'm taking that piece of candy away from ya."

_Prerequisite: for JWT Authentication here, I'm using a package called [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken). Make sure to install it in your project directory._

So start by defining that policy by going to `config/policies.js` and adding the following line within the `module.exports.policies` body:
```
{
   controllerName: {
      'action-name': 'isAuthenticated'
   }
}
```
That way, you told `action-name` that `isAuthenticated` will make a few checks on HTTP Requests directed to you before it decides whether or not the action can act upon that request.

Next up, you'll have to actually write that policy. Sails' CLI provides a command to [generate](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate) just about anything, including policies. So run the following command in your command line:
`sails generate policy isAuthenticated`
if all goes well, you will see a `isAuthenticated.js` file inside `api/policies/` directory.

Write the following code inside the `isAuthenticated.js` policy:

**CODE**
```
/**
 * isAuthenticated
 *
 * @module      :: Policy
 * @description :: Simple policy to require an authenticated user, or else redirect to login page
 *                 Looks for an Authorization header bearing a valid JWT token
 * @docs        :: http://sailsjs.org/#!documentation/policies
 *
 */

module.exports = async function (req, res, next) {
    sails.helpers.verifyJwt.with({
        req: req,
        res: res
    })
        .switch({
            error: function (err) {
                return res.serverError(err)
            },
            invalid: function (err) {
                // if this is not an HTML-wanting browser, e.g. AJAX/sockets/cURL/etc.,
                // send a 401 response letting the user agent know they need to login to
                // access this endpoint.
                if (req.wantsJSON) {
                    return res.sendStatus(401)
                }
                // otherwise if this is an HTML-wanting browser, do a redirect.
                return res.redirect('/login')
            },
            success: function () {
                // user has been attached to the req object (ie logged in) so we're set, they may proceed
                return next()
            }
        })
}

```

**EXPLANATION**
First off, this policy is getting some help from a helper called `verifyJwt`, which we'll write in a minute. It's also giving it two parameters. The request `req` and response `res`. At first I was confused as to how am I going to pass these parameters to `isAuthenticated` from my `policies.js` definition? As it turns out, sails takes care of that automatically, since policies are intended, by nature, to take `req` and `res`, and they are only needed for HTTP Requests.

`verifyJwt` will return either `error`, or `invalid`, or `success`. Each one of these possible returns has its own handling.
If an `error` is returned, bad news. You've got a problem in your code or in your request.
If an `invalid` is returned, good news for you. Your code's working, but the request won't be forwarded to your action because the request is not authenticated.
If a `success` is returned, the user is authenticated, the request is forwarded to your action and everybody's happy.

Now onto the `verifyJwt` helper function. This is going to be the bulk of your authentication logic.

To do that, we'll have to create a helper
`sails generate helper verify-jwt`.

inside `api/helpers/verify-jwt.js`, we will write
**CODE**
```
var jwt = require('jsonwebtoken')

module.exports = {
  friendlyName: 'Verify JWT',
  description: 'Verify a JWT token.',
  inputs: {
    req: {
      type: 'ref',
      friendlyName: 'Request',
      description: 'A reference to the request object (req).',
      required: true
    },
    res: {
      type: 'ref',
      friendlyName: 'Response',
      description: 'A reference to the response object (res).',
      required: false
    }
  },
  exits: {
    invalid: {
      description: 'Invalid token or no authentication present.',
    }
  },
  fn: function (inputs, exits) {
    var req = inputs.req
    var res = inputs.res
    if (req.header('authorization')) {
      // if one exists, attempt to get the header data
      var token = req.header('authorization').split('Bearer ')[1]
      // if there's nothing after "Bearer", no go
      if (!token) return exits.invalid()
      // if there is something, attempt to parse it as a JWT token
      return jwt.verify(token, process.env.JWT_KEY, async function (err, payload) {
        if (err || !payload.sub) return exits.invalid()
        var user = await User.findOne(payload.sub)
        if (!user) return exits.invalid()
        // if it got this far, everything checks out, success
        req.user = user
        return exits.success(user)
      })
    }
    return exits.invalid()
  }
}
```
**EXPLANATION**
- Firstly, the helper is checking for an `authorization` header inside the request. If it doesn't exist, then the request is unauthenticated and will `return exits.invalid();
- If an `authorization` header **does** exist, we extract the `token`, and run `jsonwebtoken`'s `verify` function. This function will return a `payload`.
- A `sub` property inside the `payload` should exist, as per the [JWT Anatomy](https://jwt.io/)
- If it does, it will most likely hold a key identifier for the user (e.g. ID).
- Once we have that `sub`, we need to use it to try to find a user, to whom this JWT belongs.
- If this user is found, assign it to your `req`. The reason why we're doing that is, if, for some reason  in our code, we need to verify that a certain operation is being requested by userA, who indeed is authenticated, on some belongings to userB. So even though userA is authenticated, they should not be allowed to perform any actions concerning userB (such as, editing a userB's blog post).

And voilà. You've got authentication up and running.

If you have any questions, feedback, corrections from any misinformation I might have provided (including typos), my direct messages are open for anyone but I strongly urge you to post them in the comments section for everyone to benefit from them.

Happy coding fellas!