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-laravelish-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 written by Corey Birnbaum. 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
- Authentication Valid -- Redirect to controller
- Authentication invalid -- 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.
In simple terms, a middleware in Sails is called a policy. 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. 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 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 thetoken
, and runjsonwebtoken
'sverify
function. This function will return apayload
. - A
sub
property inside thepayload
should exist, as per the JWT Anatomy - 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!
Top comments (15)
sails generate policy isAuthenticated
When i tried the above command, i got the following error:
error: No generator called
policy
found.Did you mean
sails generate api policy
?Tip: Want to use a custom or community generator?
Add it to your app's .sailsrc file under
modules
:You can use a relative path:
Or the name of an NPM package:
For help, see:
sailsjs.com/docs/concepts/extendin...
Thank you for the feedback!
At the time of this article, I was relying on Sails' Documentation, which currently takes us to a dead link. So it could be that it's changed. I've posted a comment with the updates
Thank you again!
As per @pragathyt95 ,
sails generate policy [policy-name]
is not working. After trying to navigate to Sails' Documentation, it led me to a dead link, which could mean that it's no longer there.The concept and everything else will still work as they should! All what you need to do is manually creating the policy inside
api/policies/<policy_name.js>
Great article, in reference to community, there is a brewing community for Sails at the moment and a platform that will be launching soon to teach Sails sailscasts.com
You can join the community by following this invite
discord.gg/gbJZuNm
We also have a blog dedicated to learning Sails things
blog.sailscasts.com
By the way I wrote a LogRocket article on building a web API with Sails packed with user registration, JWT authentication, email verification and more.
Check it out if you are interested in that sort of thing :)
blog.logrocket.com/building-a-node...
how to run this?
How to run what exactly?
i got it cleared thank u!
Alright Prathika. If there's anything else I can do, let me know!
Thanks for the article, being a sails fan I've been looking for a built in way of automatically handling JWT tokens, this really helps 😀
If there's anything else I can help you with, please let me know!
Hello, there! Thanks for the quick solution.
However, a crucial part is missing here - how do we generate the web tokens at the user level?
Hey there Plamen!
Unfortunately, this article does not have the purpose of teaching how JWT Tokens are generated.
If you'd like, and other people would too, I can create an article that serves that purpose! Let me know and help me get supporters for the purpose!
Thanks for this article it really helped!
If you need anything else make sure to ping me!