How to pass JWT via Header and retrieve data through Server Side Views in Sails.js

Vijay Rajasekaran picture Vijay Rajasekaran · Jan 15, 2016 · Viewed 7.8k times · Source

Backend: Sails.js

I have already implemented JSON Web Token for the REST API, where all AJAX requests from the front-end to the actions of various controllers return data res.json(data); only if it receives a correct JWT via Authorization Header. It works as expected.

Now, i have created a view using EJS with <%= name =>, now a controller has the details res.view('/home', {name: "Bill"} ); and configured the routes to point to the respective controller too.

How do i retrieve "name" from the controller by authenticating with an already issued JWT (stored in localStorage) from a Server Side View with the attribute <%= name =>

Thanks.

CODE:

views/index.ejs

<%= name =>

api/controllers/UsersController.js

info: function(req, res) {

var userId = req.token;

Users.findOne(userId).exec(function(err, profile) {
    if(err) {
    res.json(err);
    } else {
       res.view('index',{
        name: profile.name,
    });
    }
});        
},

api/policies/tokenAuth.js

module.exports = function(req, res, next) {
  var token;

  if (req.headers && req.headers.authorization) {
    var parts = req.headers.authorization.split(' ');
    if (parts.length == 2) {
      var scheme = parts[0],
        credentials = parts[1];

      if (/^Bearer$/i.test(scheme)) {
        token = credentials;
      }
    } else {
      return res.json(401, {err: 'Format is Authorization: Bearer [token]'});
    }
  } else if (req.param('token')) {
    token = req.param('token');
    delete req.query.token;
  } else {
    return res.json(401, {err: 'No Authorization header was found'});
  }

  sailsTokenAuth.verifyToken(token, function(err, token) {
    if (err) return res.json(401, {err: 'Please Logout and Login again!'});

    req.token = token;

    next();
  });
};

api/services/sailsTokenAuth.js

var jwt = require('jsonwebtoken');

module.exports.issueToken = function(payload) {
  var token = jwt.sign(payload, process.env.TOKEN_SECRET || "xxxxxxx");
  return token;
};

module.exports.verifyToken = function(token, verified) {
  return jwt.verify(token, process.env.TOKEN_SECRET || "xxxxxxx", {}, verified);
};

config/routes.js

module.exports.routes = {
    '/': {
        controller: 'UsersController',
        action: 'info'
      },
};

SOLUTION:

To use JWT as an authenticating factor from client browser to server, either localStorage or session cookie can be used. I have finally narrowed it down to using the cookie since due to easier implementation in the server.

After authenticating the user, store the JWT in the clients browser,

res.cookie('jwttoken', sailsTokenAuth.issueToken(profile.name));

Then it can be retrieved using req.cookies.jwttoken and verify it through a service (jwt.verify) to give access to requested information via res.json or res.view

Answer

arcseldon picture arcseldon · Jan 16, 2016

Can't see any of your code, as not provided, but lets say that you set up a custom policy api/policies/jwt.js etc, and in there you check the JWTToken on each request. Something like (ignoring any error handling for simplicity):

module.exports = function (req, res, next) {

    var token = req.headers.authorization.split(' ')[1];
    var payload = jwt.decode(token, config.TOKEN_SECRET);
    req.userId = payload.sub;
    next();
};

Notice how I have appended the userId onto the req object (req.userId = payload.sub;). That means in your controller, you will have access to the userId (or in your case whatever id you are using to identify your logged in user etc), as you can simply reference it from the incoming req object.

Obviously your implementation of the JWT policy may vary, but just customize it to something like above. If you actually kept the name in the jwt token itself (cannot see your implementation) then simply append this to the req object (no model lookup needed).

Once you have the needed name, pass that to your view (as you are currently doing with hardcoded value Bill above.

Update based on updated question

So based on your comment and updated question, you have managed to extract the profile information from JWT token, and get required profile info from a model lookup. You are now unable to get that name presented on the EJS view.

So you could achieve this something like:

Controller:

res.view('index', {
        name: profile.name
    }

Notice I removed the comma after profile.name ;)

config/views.js should be set to "ejs" if that is the templating engine you are using.

The rest looks good. At least, consistent with what I would have expected.

Your best bet is to either explicitly use a debugger, or at least console.log statements right up to point just before the controller.info method returns, and ensure the profile.name is set as expected. If you get that far, then you'll know that the problem is with the routing to view somehow.

You could try creating a subfolder under views and also rename the view file (index is slightly special..). Do something like views/xxx/yyy.ejs

Also temporarily comment out your controller.info method, and replace it with something really simple like:

res.view('xxx/yyy', {
            name: 'bob'
        });

Further update based on further user feedback

From your last comment, your question is gradually evolving...

What I think you are now saying is that the Sails code is all working, and proven working using Postman. But failing when using a web browser client because your client code isn't passing the correct Token information in the http header.

If that is the case, you'll need create an Interceptor on the Client that applies the header information programmatically.

Eg. (just a snippet taken from out of an Angular factory authInterceptor, but nothing angular specific shown here):

 request: function (config) {
      var token = authToken.getToken();

      if (token) {
        config.headers.Authorization = 'Bearer ' + token;
      }

      return config;
    },

Above, on request, the JWT is retrieved from local storage (which you say you have working), and if the token exists, then the http headers information is updated with an authorization header of the form 'Bearer token'.

I haven't looked at your repo, and to be honest I do feel I have given you ALOT of information already. Based on what I have provided, and perhaps a little more googling / testing you should be able to get a working solution.