Sara Ree
Sara Ree

Reputation: 3543

How can I access (req , res) inside a middleware?

Here is my Authorize Middleware, I use it like this in a route:

router.get('/dashboard', authorize(), dashboard);

So as you see only authorized users can access the dashboard right?

And here is the function:

const jwt = require('express-jwt');

module.exports = authorize;

function authorize(roles = []) {
    // roles param can be a single role string (e.g. Role.User or 'User') 
    // or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
    if (typeof roles === 'string') {
        roles = [roles];
    }

    return [
        // authenticate JWT token and attach user to request object (req.user)
        jwt({ secret, algorithms: ['HS256'] }),

        // authorize based on user role
        async (req, res, next) => {
            const account = await db.Account.findById(req.user.id);
            const refreshTokens = await db.RefreshToken.find({ account: account.id });

            if (!account || (roles.length && !roles.includes(account.role))) {
                // account no longer exists or role not authorized
                return res.status(401).json({ message: 'Unauthorized' });
            }

            // authentication and authorization successful
            req.user.role = account.role;
            req.user.ownsToken = token => !!refreshTokens.find(x => x.token === token);
            next();
        }
    ];
}

This is the explanation of what the function exactly do (you can skip it for now):

The authorize middleware can be added to any route to restrict access to the route to authenticated users with specified roles. If the roles parameter is omitted (i.e. authorize()) then the route will be accessible to all authenticated users regardless of role. It is used by the accounts controller to restrict access to account CRUD routes and revoke token route.

The authorize function returns an array containing two middleware functions:

The first (jwt({ ... })) authenticates the request by validating the JWT access token in the "Authorization" header of the http request. On successful authentication a user object is attached to the req object that contains the data from the JWT token, which in this example includes the user id (req.user.id).

The second authorizes the request by checking that the authenticated account still exists and is authorized to access the requested route based on its role. The second middleware function also attaches the role property and the ownsToken method to the req.user object so they can be accessed by controller functions. If either authentication or authorization fails then a 401 Unauthorized response is returned.

And this is what I want to do:

I want to add the jwtToken to Authorization headers when this middleware is fired ok?

So I added this to the beginning of the function :

function authorize(roles = []) {
    // roles param can be a single role string (e.g. Role.User or 'User') 
    // or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
    if (typeof roles === 'string') {
        roles = [roles];
    }

    // This is the code I added
    const jwtToken = req.cookies.jwtToken; // get the jwtToken from cookie
    const token = `Bearer ${jwtToken}`; // format the token
    res.header('Authorization', 'Bearer '+ token); // simply add the jwtToken to headers


    return [
        // authenticate JWT token and attach user to request object (req.user)
        jwt({ secret, algorithms: ['HS256'] }),

   ...

No matter how good I try I'm unable to to this because I have not access to req and res inside the middleware!

How can I access req and res inside the authorize middleware?

Upvotes: 0

Views: 1071

Answers (3)

slebetman
slebetman

Reputation: 114014

The problem is you are returning an array:

return [
    jwt({ secret, algorithms: ['HS256'] }),

    // authorize based on user role
    async (req, res, next) => { /* ... */ }
]

and Express does not know what to do with it. Express expects a function as a middleware (your async (req,res,next) => ...).

One way around this is to use the spread operator to pass the jwt middleware and your middleware separately to Express:

router.get('/dashboard', ...authorize(), dashboard);

This works but is not quite expected by other developers who are used to Express. It kinds of looks weird.

Now there is nothing wrong with weird looking code but I think there is a better way to do this - use a router to chain your middlewares:

const jwtMiddleware = jwt({ secret, algorithms: ['HS256'] });

function authorize(roles = []) {
    // roles param can be a single role string (e.g. Role.User or 'User') 
    // or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
    if (typeof roles === 'string') {
        roles = [roles];
    }

    let midRouter = express.Router();

    midRouter.use(jwtMiddleware);
    midRouter.use(async (req, res, next) => {
          const account = await db.Account.findById(req.user.id);
          const refreshTokens = await db.RefreshToken.find({ account: account.id });

          if (!account || (roles.length && !roles.includes(account.role))) {
              // account no longer exists or role not authorized
              return res.status(401).json({ message: 'Unauthorized' });
          }

          // authentication and authorization successful
          req.user.role = account.role;
          req.user.ownsToken = token => !!refreshTokens.find(x => x.token === token);
         // authorize based on user role
         next();
    });

    return midRouter;
}

Now you can use your original code:

router.get('/dashboard', authorize(), dashboard);

Personally I wouldn't bother with this and just use express-jwt the way it is documented to be used:

router.use(jwt({ secret, algorithms: ['HS256']}));
router.get('/dashboard', authorize(), dashboard);

This is what most Express developers would expect to see and you can remove the jwt part out of you authorize() function.

Remember that you can define a list of paths you don't want jwt authentication to happen in its .unless() method.

Upvotes: 0

Barski
Barski

Reputation: 31

authorize is not a middleware. It executes middleware, so all you need to do is move your code from authorize down to (req, res, next) => {} function and return it.

Upvotes: 0

Quentin
Quentin

Reputation: 944392

How to solve this with your approach

The authorize function is not middleware.

It is a function that, when you call it, returns two middleware functions (in an array).

The first middleware function is the return value of:

// authenticate JWT token and attach user to request object (req.user)
jwt({ secret, algorithms: ['HS256'] }),

It creates the user property on the req object.

The second middleware function is this one:

// authorize based on user role
async (req, res, next) => {
    // etc

To get access to the req object, do so inside that function.

However, it seems you want to access it before the middleware returned by jwt({ secret, algorithms: ['HS256'] }), runs.

To do that, you need to add a third middleware function at the front of the array.

return [
    (req, res, next) => {
        // access cookies and transfer to header variable here
        next();
    },

    // authenticate JWT token and attach user to request object (req.user)
    jwt({ secret, algorithms: ['HS256'] }),

The correct approach

Look at the documentation for the module you are using:

Customizing Token Location

A custom function for extracting the token from a request can be specified with the getToken option. This is useful if you need to pass the token through a query parameter or a cookie. You can throw an error in this function and it will be handled by express-jwt.

app.use(jwt({
  secret: 'hello world !',
  credentialsRequired: false,
  getToken: function fromHeaderOrQuerystring (req) {
    if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
        return req.headers.authorization.split(' ')[1];
    } else if (req.query && req.query.token) {
      return req.query.token;
    }
    return null;
  }
}));

So you can pass a getToken function that reads the cookie directly instead of fiddling about trying to rewrite the request.

Upvotes: 2

Related Questions