This project extends from some previous posts on creating a SPA style application with Node (Sails) and Aurelia. I’m not going to go into detail about installing Node, NPM, or Sails, other than when it’s germane to the subject. I’m assuming you are already set up with all of the needed basics for Node/Sails development. If not, visit the Sails site to get started.
We’ll start by creating a new directory in which to create our test projects and bringing up a command line. What we’re creating here is an API; so no need for any front end mixed into the Sails application. We can create that later if we like. So the command to create a new app without a front end is:
sails new myApi --no-frontend
The basics of the app should now be present in the “myApi” directory (or whatever you called it). If you open up the project in your favorite editor, you will see the following structure:
Most of the action will happen in the api folder but we’ll also need to set up some config settings. But first let’s go ahead and install some dependencies. Make sure you are in the root folder of your project and install the following from the command line:
npm install jsonwebtoken --save npm install bcrypt-nodejs --save npm install passport --save npm install passport-jwt --save npm install passport-local --save
Alternately, you could just add these to the package.json file and run npm install.
Config
Now we’ll add our passport configuration. Another nice thing about Sails is that putting a js file in the /config folder means that it will be run when you “lift” or start the app. In the config folder, create passport.js and add the following code:
/** * Passport configuration file where you should configure strategies */ var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; var JwtStrategy = require('passport-jwt').Strategy; var EXPIRES_IN_MINUTES = 60 * 24; var SECRET = process.env.tokenSecret || "4ukI0uIVnB3iI1yxj646fVXSE3ZVk4doZgz6fTbNg7jO41EAtl20J5F7Trtwe7OM"; var ALGORITHM = "HS256"; var ISSUER = "nozus.com"; var AUDIENCE = "nozus.com"; /** * Configuration object for local strategy */ var LOCAL_STRATEGY_CONFIG = { usernameField: 'email', passwordField: 'password', passReqToCallback: false }; /** * Configuration object for JWT strategy */ var JWT_STRATEGY_CONFIG = { secretOrKey: SECRET, issuer : ISSUER, audience: AUDIENCE, passReqToCallback: false }; /** * Triggers when user authenticates via local strategy */ function _onLocalStrategyAuth(email, password, next) { User.findOne({email: email}) .exec(function (error, user) { if (error) return next(error, false, {}); if (!user) return next(null, false, { code: 'E_USER_NOT_FOUND', message: email + ' is not found' }); // TODO: replace with new cipher service type if (!CipherService.comparePassword(password, user)) return next(null, false, { code: 'E_WRONG_PASSWORD', message: 'Password is wrong' }); return next(null, user, {}); }); } /** * Triggers when user authenticates via JWT strategy */ function _onJwtStrategyAuth(payload, next) { var user = payload.user; return next(null, user, {}); } passport.use( new LocalStrategy(LOCAL_STRATEGY_CONFIG, _onLocalStrategyAuth)); passport.use( new JwtStrategy(JWT_STRATEGY_CONFIG, _onJwtStrategyAuth)); module.exports.jwtSettings = { expiresInMinutes: EXPIRES_IN_MINUTES, secret: SECRET, algorithm : ALGORITHM, issuer : ISSUER, audience : AUDIENCE };
So there’s a few things going on here. First, we’re importing both passport and the two strategies we’re going to set up for now (local and JWT). We’ll add social login in the next post. Next, we’re going to add configuration for our two auth strategies. For the local strategy we’ll use email and password in to login. For JWT, we’ll set up the parameters needed to verify the token: secret, issuer and audience. It should be noted again that the issuer and audience are optional but provide some additional verification.
Next we’ll add some functions to react to the auth request for each strategy. For local auth, this means making sure that the user matches the user in our database and returning a false response with a message on failure or the user if it succeeded. For JWT auth, the strategy is already validating the token internally. If you want additional validation here you can check the user ID against the database to verify that this user actually exists and is active. For the sake of simplicity, I’m going to trust the token and retrieve the user information from the token payload and just return it.
Last, we are exporting some settings to be used elsewhere in the API. This is another nice feature of Sails: The ability to export settings in a config file to make them available globally. So in our case, we are exporting jwtSettings from our config. To access these settings from anywhere, we can use the global accessor: sails.config.jwtSettings.
Services
Next add a new file under api/services called CipherService. In sails, services and models are named using PascalCase by convention. I need a better name for this one but it will do for the time being. This is where we’ll put all of our code that does hashing and/or token stuff.
var bcrypt = require('bcrypt-nodejs'); var jwt = require('jsonwebtoken'); module.exports = { secret: sails.config.jwtSettings.secret, issuer: sails.config.jwtSettings.issuer, audience: sails.config.jwtSettings.audience, /** * Hash the password field of the passed user. */ hashPassword: function (user) { if (user.password) { user.password = bcrypt.hashSync(user.password); } }, /** * Compare user password hash with unhashed password * @returns boolean indicating a match */ comparePassword: function(password, user){ return bcrypt.compareSync(password, user.password); }, /** * Create a token based on the passed user * @param user */ createToken: function(user) { return jwt.sign({ user: user.toJSON() }, sails.config.jwtSettings.secret, { algorithm: sails.config.jwtSettings.algorithm, expiresInMinutes: sails.config.jwtSettings.expiresInMinutes, issuer: sails.config.jwtSettings.issuer, audience: sails.config.jwtSettings.audience } ); } };
We’ll use these methods elsewhere. The hash/comparePassword methods are pretty self-explanatory. Bcrypt-nodejs even adds in a salt to the hashed password by default. Pretty nice. The createToken method uses JsonWebToken to create a new token using the sign method. This method accepts a payload (the user in our case), a secret to use for the self-contained JWT hash, and some metadata including the algorithm, expiration, issuer and audience. Issuer and Audience will also be validated when the token is verified coming back in, so it gives a little extra protection. The inclusion of the user in the payload is really just for example. In the real world, you might include the user id and claims etc…
API Generation
Next we’ll create our User API. Return to the root command line of the application and run the following command:
sails generate api User
If you now observe your controllers and models directories, you’ll see the generated controller and model for User. The controller can be left as-is for now. Although it looks empty, it’s functional! It will use default blueprints included with Sails to provide functionality. Now we’ll want to enhance the model. Open the api/models/User file and add the following code:
/** * User * @description :: Model for storing users */ module.exports = { schema: true, attributes: { username: { type: 'string', required: true, unique: true, alphanumericdashed: true }, password: { type: 'string' }, email: { type: 'string', email: true, required: true, unique: true }, firstName: { type: 'string', defaultsTo: '' }, lastName: { type: 'string', defaultsTo: '' }, photo: { type: 'string', defaultsTo: '', url: true }, socialProfiles: { type: 'object', defaultsTo: {} }, toJSON: function () { var obj = this.toObject(); delete obj.password; delete obj.socialProfiles; return obj; } }, beforeUpdate: function (values, next) { CipherService.hashPassword(values); next(); }, beforeCreate: function (values, next) { CipherService.hashPassword(values); next(); } };
Here we’re adding a bunch of properties to our User schema, but also a method to remove sensitive data before converting our object to JSON. We also have beforeUpdate and beforeCreate delegates defined to hash our password before saving to the data store. You can see that the hash will use our CipherService, and one of the nice things about sails is that it makes our services available globally. So here we can just use CipherService instead of needing a require. In Sails, you also have models available globally using their name. This is a huge advantage of Sails.
Auth Controller
Now we have to add an Auth endpoint to perform signup/signin etc… We’re only generating a controller here, so you can just add the js file or do it the Sails way:
sails generate controller Auth
Now that we have our AuthController, let’s add the following actions:
/** * AuthController * @description :: Server-side logic for manage user's authorization */ var passport = require('passport'); /** * Triggers when user authenticates via passport * @param {Object} req Request object * @param {Object} res Response object * @param {Object} error Error object * @param {Object} user User profile * @param {Object} info Info if some error occurs * @private */ function _onPassportAuth(req, res, error, user, info) { if (error) return res.serverError(error); if (!user) return res.unauthorized(null, info && info.code, info && info.message); return res.ok({ // TODO: replace with new type of cipher service token: CipherService.createToken(user), user: user }); } module.exports = { /** * Sign up in system * @param {Object} req Request object * @param {Object} res Response object */ signup: function (req, res) { User .create(_.omit(req.allParams(), 'id')) .then(function (user) { return { // TODO: replace with new type of cipher service token: CipherService.createToken(user), user: user }; }) .then(res.created) .catch(res.serverError); }, /** * Sign in by local strategy in passport * @param {Object} req Request object * @param {Object} res Response object */ signin: function (req, res) { passport.authenticate('local', _onPassportAuth.bind(this, req, res))(req, res); }, };
So here we’re doing two basic things: Sign up and Sign in. The signup method uses the built-in Create method provided by the user model to create a new user. The signin used the Passport’s local authorization strategy to log in. In both cases, if it succeeds, we’ll generate a token so that they are signed in automatically. All requests that follow should now include the returned token in the header. We’ll demo this shortly, but have one final item to add first: The login policy.
Policies
In Sails, the way to add Express middleware is via policies. If you examine the policy structure, that’s really exactly what it is. In our case, we need a policy that will protect our non-auth controllers from requests that don’t have a token. Look at the api/policies folder. There may be a sessionAuth.js file already present. You can delete this file as we won’t use it. Now add a new file to api/policies called isAuthenticated.js with the following contents:
/** * isAuthenticated * @description :: Policy to inject user in req via JSON Web Token */ var passport = require('passport'); module.exports = function (req, res, next) { passport.authenticate('jwt', function (error, user, info) { if (error) return res.serverError(error); if (!user) return res.unauthorized(null, info && info.code, info && info.message); req.user = user; next(); })(req, res); };
The policy is pretty simple: It authenticates the user using the ‘jwt’ strategy that we earlier implemented in the passport configuration. If no token is present, this will return an unauthorized response. Otherwise it will add the user object from the token to the request.
Responses
Let’s briefly discuss Sails responses, another cool Sails convention. If you take a look at api/responses, you’ll see a bunch of responses that have been created for you already. These make it easy to define and reuse typical responses. So to return an OK response, instead of defining our response over and over, we can just do: return res.ok(data). In our case, we need to add two new custom responses: created and unauthorized. In the responses folder add created.js with the following content:
/** * 201 (Created) Response * Successful creation occurred (via either POST or PUT). * Set the Location header to contain a link * to the newly-created resource (on POST). * Response body content may or may not be present. */ module.exports = function (data, code, message, root) { var response = _.assign({ code: code || 'CREATED', message: message || 'The request has resulted in a new resource being created', data: data || {} }, root); this.req._sails.log.silly('Sent (201 CREATED)\n', response); this.res.status(201); this.res.jsonx(response); };
Now create an unauthorized.js file in the same folder:
/** * 401 (Unauthorized) Response * Similar to 403 Forbidden. * Specifically for authentication failed or not yet provided. */ module.exports = function (data, code, message, root) { var response = _.assign({ code: code || 'E_UNAUTHORIZED', message: message || 'Missing or invalid authentication token', data: data || {} }, root); this.req._sails.log.silly('Sent (401 UNAUTHORIZED)\n', response); this.res.status(401); this.res.jsonx(response); };
I’ve also customized the other responses in accordance with some guidance provided by the Sails API Yeoman generator, but I’m not going to go through all of them. If you would like to copy them, feel free to reference the project on GitHub. While you’re there, also checkout the overridden Blueprints in api/blueprints. These let you customize the standard processing of different operations exposed by the api controllers (unless you override them specifically in the controller). I also took these blueprints from the Sails API Yeoman generator.
Now we have our policy…how do we use it? In the /config folder, you should see a policies.js file. Open this file and adjust the code to the following:
module.exports.policies = { '*': ['isAuthenticated'], AuthController: { '*': true } };
This is applying the following rules:
- Protect all controllers from unauthenticated users.
- Override this for the AuthController and allow anybody to hit that.
Loose Ends
OK, before we test this thing out, just a couple of small cleanups that will make Sails bug you less on “lift”. First open /config/models.js. Lets set up a standard migration policy so it won’t bug us on every project start. Uncomment or add the line that reads migrate and change it to ‘drop‘ for now. This will drop the data store each time the application runs. You can change to ‘alter‘ if you don’t want this behavior.
migrate: 'drop'
Now we’ll tell Sails not to expect a Gruntfile. When you create Sails with –no-frontend, it doesn’t scaffold a Gruntfile, but it still warns that there isn’t one on every start. Go to /.sailssrc and remove the Grunt hook:
{ "generators": { "modules": {} }, "hooks":{ "grunt":false } }
Testing it Out!
Wow…that was a lot of stuff to run through but we’re basically done! Let’s test it out. If you don’t already have Postman (or you’re preferred test tool) installed, go ahead and install it.
Now lets start up our Api. At your project root terminal type:
sails lift
You should see sails start up on port 1337 by default. Now fire up Postman and signup a user by posting the following JSON payload to your local signup endpoint. In my case, this is: http://localhost:1337/auth/signup
{ "username":"testdude", "email":"test1@test.com", "password":"testdude" }
The response should look like:
{ "code": "CREATED", "message": "The request has been fulfilled and resulted in a new resource being created", "data": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoidGVzdGR1ZGUiLCJlbWFpbCI6InRlc3QxQHRlc3QuY29tIiwiZmlyc3ROYW1lIjoiIiwibGFzdE5hbWUiOiIiLCJwaG90byI6IiIsImNyZWF0ZWRBdCI6IjIwMTUtMDQtMjRUMjE6NTg6MTkuMjcxWiIsInVwZGF0ZWRBdCI6IjIwMTUtMDQtMjRUMjE6NTg6MTkuMjcxWiIsImlkIjoyfSwiaWF0IjoxNDI5OTEyNjk5LCJleHAiOjE0Mjk5OTkwOTksImF1ZCI6Im5venVzLmNvbSIsImlzcyI6Im5venVzLmNvbSJ9.j9mSeoHJiNb_rzxqJ8Cefv5ctcMVzbgvnUlvAWhbXas", "user": { "username": "testdude", "email": "test1@test.com", "firstName": "", "lastName": "", "photo": "", "createdAt": "2015-04-24T21:58:19.271Z", "updatedAt": "2015-04-24T21:58:19.271Z", "id": 2 } } }
You’ll notice that a token was generated. Copy the token that your API generated so that it’s available later.
We’ll now attempt to signin as well. Post the following JSON to your local signin endpoint. In my case, this is: http://localhost:1337/auth/signin. Earlier in our Passport config we set up local auth to use email and password, but we could change this to use the username if that is preferable.
{ "email":"test1@test.com", "password":"testdude" }
In this case, the response should be almost identical but the response code will be a 200-OK instead of a 201-Created.
Now lets try to access a resource without our token. Perform a get on your local user endpoint. In my case: http://localhost:1337/user
You should get a response indicating that you are not authorized:
{ "code": "E_UNAUTHORIZED", "message": "No auth token", "data": {} }
Now lets update our request to add an Authorization header. For the value, add “JWT“, then a space, then the token you created previously. So an example value would be:
JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoidGVzdGR1ZGUiLCJlbWFpbCI6InRlc3QxQHRlc3QuY29tIiwiZmlyc3ROYW1lIjoiIiwibGFzdE5hbWUiOiIiLCJwaG90byI6IiIsImNyZWF0ZWRBdCI6IjIwMTUtMDQtMjRUMjE6NTg6MTkuMjcxWiIsInVwZGF0ZWRBdCI6IjIwMTUtMDQtMjRUMjE6NTg6MTkuMjcxWiIsImlkIjoyfSwiaWF0IjoxNDI5OTEzNTI3LCJleHAiOjE0Mjk5OTk5MjcsImF1ZCI6Im5venVzLmNvbSIsImlzcyI6Im5venVzLmNvbSJ9.9FqGAVnJNM3SWRupOqCoW7tPJqu0ChZt5f2_En6GKqo
Now send the Get request and you should get back the user record we created when we signed up!
{ "code": "OK", "message": "Operation is successfully executed", "data": [ { "username": "testdude", "email": "test1@test.com", "firstName": "", "lastName": "", "photo": "", "createdAt": "2015-04-24T21:58:19.271Z", "updatedAt": "2015-04-24T21:58:19.271Z", "id": 2 } ] }
Final Thoughts
Seems like we did a lot of stuff here, but it was all pretty easy thanks to the conventions and code generation provided by Sails. In upcoming articles, we’ll look at expanding this example to include social auth, a front end via Aurelia and additional storage mechanisms (we’re just using disk here).
The code for this example is available here. The code in my uploaded GitHub example is using Postgresql instead of writing to disk, but that can easily be changed in the config/models.js file. Just point to any connection you have set up in the config/connections.js file.
** Warnings **
Always remember that this style of token auth is not secure if the token can be intercepted. In production, always perform communications with the API (at least those that send the token or any sensitive information) over SSL.
Additionally, you don’t want to check any production secrets into public source control. In this case, the secret in the passport config file. In a follow up we’ll talk about how to handle this but you can read about config overrides here.
Really good article have been looking for a good tutorial on this.
I have also looked at adding roles/levels of access witch should be done with a specific claim i think but i’m not sure how exactly to do that and verify access to specific api’s/controllers depending if role.
Would be really nice if you could add it.
LikeLiked by 1 person
Sure, I’ll look into adding that.
LikeLiked by 1 person
Hi there, thanks for such simple and complete tutorial!
I would like to ask you if you have worked with the roles/levels part that Oscar mentioned? If you, please can you share the link please!
LikeLiked by 1 person
Thank you for the great tutorial, I found it really helpful. I would love to know how to add user roles and access level too.
LikeLike
Really thank you for this great post.
Just a minor issue with the github code:
‘cipherService.js’ should be ‘CipherService.js’ otherwise an ‘E_INTERNAL_SERVER_ERROR’ is thrown.
Keep ’em coming 🙂
LikeLike
Thanks for the feedback! Will take a look.
LikeLike
Nice Tutorial on JWT with sails
I want to implemet logout functionality, how can i do it?
LikeLike
Once the user loses/destroys/forgets their token, they are essentially logged out.
So if you were using your Sails API via a JS client (or any client for that matter), all you would need to do is destroy or nullify that user’s token when they “logout”.
Additionally, you can change the server secret to invalidate the tokens that way as well (though this would be for EVERY user). Using this post as an example, you’d change var SECRET = process.env.tokenSecret inside passport.js, to invalidate the JWTs across the board.
LikeLiked by 1 person
Awesome write up. I’ve been playing around with node/sails/express recently and have been struggling to get a working app that uses just JWT’s for auth. This article is pretty much exactly what I was trying to do. I look forwartd to you doing a follow up using aurelia, I’ve been keeping an eye on aurelia and would like to use it sometime.
On a sidenote I NEVER leave comments on random blogs I stumble across but had to say thanks.(so thanks!)
LikeLike
Thanks for the kind words Mike…hopefully will have time soon to answer questions and follow up with some more posts
LikeLike
Thank you so much. Same as you I have been trying to get sails to work with SPA but all examples are with Server UI except yours, I have to say a big thank you.
After local login, I want to implement Facebook and Twitter login. I see that you have included in the node_modules.
Can you give me more pointers?
Example of usage etc?
Thank you so much in advance
LikeLike
Using sails 0.11.0, there’s an issue with validating emails:
an error displayed:
“email” validation rule failed for input:
although valid emails are being used.
The solution is simply remove email: true from User attributes and add type: ’email’ instead
LikeLike
This was fixed in the latest version of generator-sails-rest-api. You just need to change type: ‘string’ in your model to type: ’email’
LikeLike
Thanks Eugene, I need to update this article…busy with other stuff at the moment, but I’ll revisit soon.
LikeLike
Thanks for the comment!
LikeLike
Thanks for article and referencing to my repository. I’m glad that all of my work is helpful for you all 🙂
LikeLike
Thanks for a great article!
LikeLike
you save my life !
LikeLike
Man, you saved my life!
Thank you very, very much!
I am building an API with Sails, but using Mongoose (I don`t like Waterline).
With your tutorial and a bit of extra work, I managed to make it work.
I`m planing to write a tutorial (based on yours) on how to make it work with Sails+Mongoose to help others too.
Thank you.
LikeLike
Glad it helped!
LikeLike
Thanks a million. This post is amazing!
LikeLike
Thanks for the article, but could help by putting a login screen, the truth I’m just learning new Node.js and seems quite interesting …. thanks.
LikeLike
Hey Eric,
Wonderful article. Thanks a ton!
I have a question.
After the authentication and capturing the token, I am hitting one controller/action with http://localhost:1337/controller/action and then routing the action to a model based on an attribute(tenantId) in the JWToken. A sample webtoken payload is:
{
“user”: {
“email”: “test@test.com”,
“tenantId”: “HRT-9842”
},
“iat”: 1447128238,
“aud”: “nozus.com”,
“iss”: “nozus.com”
}
Could you please let me know how to decrypt this header in controller and extract the attribute so it can used to pick the model.
Thanks.
LikeLike
Hey Eric,
Wonderful article. Thanks a ton!
I have a question.
After the authentication and capturing the token, I am hitting one controller/action with http://localhost:1337/controller/action and then routing the action to a model based on an attribute(tenantId) in the JWToken. A sample webtoken payload is:
{
“user”: {
“email”: “test@test.com”,
“tenantId”: “HRT-9842”
},
“iat”: 1447128238,
“aud”: “nozus.com”,
“iss”: “nozus.com”
}
Could you please let me know how to decrypt this header in controller and extract the attribute so it can used to pick the model.
Thanks.
LikeLike
Thanks for reading Smruti, I will take a look if I get a chance…I’m currently swamped with some work and might not get back to this for a bit.
LikeLike
Hey Eric,
I am newbie so was just being a fool. Figured out the routing and now the tenants are bring routed to their respective models.
Will take some more time to understand the magic your code is doing.
Thanks again for the write up.
LikeLike
I know the feeling :). Thanks for reading my article!
LikeLike
Hey. This post is brilliant and it was working like a charm but when I run
http://localhost:1337/user
I’m getting
res.unauthorized is not a function
According to the log the error is coming from \api\policies\isAuthenticated.js
I’ve been trying for hours to figure out where I’ve gone wrong. Could you point me in the right direction? Sails noob here so I’m struggling a small bit.
Thank you for taking the time to write this post
LikeLike
I figured it out. I forgot to delete the sessionAuth which I think was causing my error. Thank you again. Brilliant post
LikeLike
Glad to know it worked out for you!
LikeLike
Excellent post. Thanks for your effort to share !!!!
LikeLike
Thanks a lot man for this awesome tut,
Now if want to return the logged in user so i can let him to update his profile, should i use => req.user?
I know how to go it with session but using jwt I am little bit confuse!
LikeLike
Thanks for sharing your thoughts on w dalszej części. Regards
LikeLike
Found a potentially serious bug with this. The JWT auth strategy does not validate JWT user credentials against the database after JWT validation, meaning if the user has been deleted after the JWT is issued they will still be able to authenticate. Additionally, the user info hashed in the JWT is then passed through the req to the controller. If the user info has been changed since the JWT was issued then this will be incorrect.
LikeLike
I wouldn’t call it a bug, but yes that a common approach depending on the needs of your app. Thanks for bringing it up.
LikeLike
That’s one amazing tutorial there Eric!!
Just one minor suggestion, in CipherService.createToken it is better to use something like
user: {username: user.username, email: user.email, id: user.id}.toJSON()
rather than user: user.toJSON()
as it’s not the best practice to include the password in the JWT Token, they can be deciphered. Yes, it will be hard to do it in the absence of SECRET key to find password from a hash. Still, we cannot deny the danger of exposing the token. +1 for mentioning SSL in the end!
LikeLike
Oopps.
It should be
var temp = user: {username: user.username, email: user.email, id: user.id}
and then,
user: temp.toJSON()
LikeLike
Great tuto! thanks, i managed to used it into my sailsjs + angular app; but I noticed something, If I login I get a token, but then if I login again, my old token would still work. Is there a way to have only one working token ? the last one generated
LikeLiked by 1 person
Same problem with me,
Please help me on this issue!
Thanks!
LikeLike
Hi,
I’m trying to build a repo to show how to use your repo from aurelia and react-native. In the past I co-authored https://github.com/SharePointOscar/MEANS and a couple node-machines [yelp.authorize.net]. Currently have to user stuff working with a login and getting back the tolken. Pretty cool and can’t thank you enough on your clear and consise repo. I decided to publish a todo front-end. I added prefix: ‘/api’, to the buleprint.js flle.
1) sails generate api todo (using mongodb)
2) create a route in routes.js’
get /api/todoclient/:id’: ‘TodoController.getclient’,
3) A method in Todocontroller.js
module.exports = {
getclient: function (req, res) {
var id = req.param(‘id’);
console.log(‘getclient todos’, id);
Todo.find({ user: id, status: ‘open’ }).exec(function (err, model) {
if (err) {
return res.negotiate(err);
}
//sails.log(‘Wow, there are %d users named Finn. Check it out:’, usersNamedFinn.length, usersNamedFinn);
return res.json({ data: model });
});
},
};
4) Using postman to test I can create user, get user with tolken but when I try to get the todos
http://www.gtz.com:9012/api/clientodo/4 using headers and replaceing the Aurhorizatrion JWT string as documented i get the following:
{
“code”: “E_NOT_FOUND”,
“message”: “The requested resource could not be found but may be available again in the future”,
“data”: {}
}
5) get http://www.gtz.com:9012/api/todo works as expected
{
“code”: “OK”,
“message”: “Operation is successfully executed”,
“data”: [
{
“title”: “111”,
“status”: “open”,
“user”: “4”,
“isComplete”: true,
“createdAt”: “2016-02-16T18:02:05.654Z”,
“updatedAt”: “2016-02-17T04:40:05.419Z”,
“open”: true,
“id”: “56d4f98aa977b58f792f271e”
},
{
“title”: “211”,
“status”: “open”,
“user”: “4”,
“isComplete”: true,
“createdAt”: “2016-02-16T18:02:05.654Z”,
“updatedAt”: “2016-02-17T04:40:05.419Z”,
“open”: true,
“id”: “56d4f993a977b58f792f271f”
}
],
“criteria”: {},
“limit”: 10,
“start”: 0,
“end”: 10,
“total”: 2
}
6) the server console is below. Would like to know what I’m doing wrong as custom route never get hit.
<- POST /api/auth/signin (684ms)
<- GET /api/user (35ms)
<- GET /api/clientodo/4 (5ms)
<- GET /api/todo (14ms)
<- GET /api/todo/4 (9ms)
I will publish soon have cleaing up the issue.
Hope to add a socket example soon after.
Thanks,
John
LikeLike
Hi,
So a re-read the new docs on bluprints (I mostly use sails10) and got everting working by taking away my routes and just referencing the methods:)
http://www.gtz.com:9012/api/todo/getclient/4 does what I expect it to do.
Less code is always better.
John
LikeLike
A small note for people coming here in 2016, the JWT method expiresInMinutes is deprecated and will result in failure when interacting with the API. Solution is to use ‘expiresIn’ instead. See this post: https://stackoverflow.com/questions/37629475/validationerror-expiresinminutes-is-not-allowed-nodejs-jsonwebtoken
LikeLike
Thanks Jimmy
LikeLike
Thank you for this great post.
One question.
I have one case as described below
Client send a sign up request -> the server will return value that including auth_token xxxx.yyyy.zzzz (option: iat and exp in yyyy)
Client send a sign in request -> Server will verify email/pwd and then will return auth_token value xxxx.yyyy.pppp.sssss (iat and exp are different.)
I used the Postman tool to request http://localhost:1337/user
Header with Authorization JWT xxxx.yyyy.zzzz success.
I also requested with JWT xxxx.pppp.ssss success.
I do not know why the xxxx.yyyy.zzzz is accepted (validated/verified)
Does the Passport save auth_token from where?
How to verify/validate the JWT? (get payload data then decode who is this (user_id, email, pwd) => I still have to query from database, again). That’s what I do not want.
Usually, I do
Once a user login I will custom/create a token based on a rule ==> insert into database and return a success notification plus that user’s token..
There is a way that check auto_token(database query/query to database) with the private APIs.
—
Please help on this issue. (revoke/refresh token).
Many thanks and Best Regards,
LikeLike
ValidationError: “expiresInMinutes” is not allowed
LikeLike
This is a fantastic post — thank you!
One note, it appears that a update to passport-jwt may now require a strategy. The repos referenced might already cover this, but just in case, the fix is easy — just add 2 lines:
Add the following near the top of the file:
var ExtractJwt = require(‘passport-jwt’).ExtractJwt;
Add the following attribute to the JWT_STRATEGY_CONFIG variable:
jwtFromRequest: ExtractJwt.fromAuthHeader()
(On the JWT_STRATEGY_CONFIG, add a comma either before or after for correct syntax).
LikeLike
I was testing with passport-jwt v3.0.0
The correct syntax should be: jwtFromRequest: ExtractJwt.fromAuthHeader({})
LikeLike
Any idea how to inject token refreshing?
I have quite clear idea how to handle invalidating tokens etc, but no idea how to put it into this system?
LikeLike
thanks a lot Mr. Eric Swann
LikeLike
Nice write up!
Is part 2 (with social) ready? I could not find.
LikeLike
I spent the entire weekend studying Sails, Passport, Sails-Auth, JWT and have gone down a dozen rabbit holes (and github repos that don’t work) trying to find something that even remotely works with a SPA. Can’t believe how Sails Passport has nothing to support JWT and not use session. You are a life saver with your sample app. Thanks so much.
LikeLike
When I’m using passport.authenticate in isAuthenticated, the authentication works, but the user is not retrieved, all I get from the user parameter is a string ‘user’. Any ideias why?
LikeLike