Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to verify authentication token of multiple type of users for a single route (without code duplication) in Express with JWT?

I have an express app where some routes are public(accessible to everyone on the internet) and some are protected. I use JWT for route authentication. I have different type of users in the system, for example: author, subscriber etc. Some of the routes are meant to be access authors and some by subscribers. But there are a few routes which can be accessed by both author and subscribers.

To make sure when a user request to protected routes, I use middleware function for each category of users to verify their authentication token.

An example route accessible to authors only like this bellow,

router.get("/", verifyToken.author(), async (req, res) => {
    try {
        // doing stuff
    } catch (err) {
        // doing stuff
    }
});

An example route accessible to subscribers only like this bellow,

router.get("/", verifyToken.subscriber(), async (req, res) => {
    try {
        // doing stuff
    } catch (err) {
        // doing stuff
    }
});

PS: verifyToken.author() or any other middleware function I wrote for the purpose of verifying token are actually closure. Because sometimes I need to pass parameter in the functions. So, an example of these methods will be,

module.exposts.author = () => {
    return (req, res, next) => {
        const token = req.header("Authorization");
        if (!token) return res.status(401).send({ message: "Access Denied!" });

        try {
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
        } catch (err) {
            res.status(401).send({ message: "Access Denied!" });
            console.log(err);
        }
    };
};

And when I need to write a function for verifying token for both author and subscriber, I can just check do so and use the method as a middleware to the required route. But I am afraid it is not as simple as it sounds because in reality I don't have just these 2 type of users. And almost all the user types has complex route access where maybe 5 particular type of user can access the route.

As I was saying, let's say author and subscriber is among the 5 user types I said above. So writing a function for author+subscriber and writing another for 5 type of users where author, subscriber exists, it seems so much code duplication.

I hope I am able to explain why I just don't want to write something like this bellow,

module.exports.authorSubscriber = () => {
    return (req, res, next) => {
        const token = req.header("Authorization");
        if (!token) return res.status(401).send({ message: "Access Denied!" });

        try {
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
        } catch (err) {
            try {
                jwt.verify(token, process.env.SUBSCRIBER_SECRET);
    
                next();
            } catch (err) {
                res.status(401).send({ message: "Access Denied!" });
                console.log(err);
            }
        }
    };
};

Instead, I want something like this, where I can re-use the function I wrote for author previously,

module.exports.authorSubscriber = () => {
    return (req, res, next) => {
        const token = req.header("Authorization");
        if (!token) return res.status(401).send({ message: "Access Denied!" });

        try {
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
        } catch (err) {
            exports.author()
        }
    };
};

But, author() won't work here. It works when called from a route because then it gets the req, res, next. But in this case, it is being called from another function and no req, res, next is passed. If I do exports.author(req, res, next) in authorSubscriber and define author to accept these parameters. it still doesn't work. I think route middleware gets the 'req, res, next` automatically, these things can't be passed manually, can they? This was the reason I made these functions closure because on a similar verifier I need to pass a certain token and middleware doesn't take extra parameters.

I am confused. You may have clearly realized that I have knowledge gaps, that's why I am missing something here.
Please help.

like image 228
dumbestCoder Avatar asked Nov 06 '22 01:11

dumbestCoder


1 Answers

Assuming you are defining the "author" middleware in a closure, as in your examples above:

module.exposts.author = () => {
    return (req, res, next) => {
        // implementation here
    };
};

Then you can indeed re-use it in the "author+subscriber" scenario you've described. But keep in mind that your middleware defined above is NOT a function named author; rather, the middleware is an anonymous function that is returned from the function named author. So if you want to re-use it in authorSubscriber, it would look something like:

module.exports.authorSubscriber = () => {
    return (req, res, next) => {
        // ...
        try {
            jwt.verify(token, process.env.AUTHOR_SECRET);

            next();
        } catch (err) {
            exports.author()(req, res, next)
        }
    };
};

Note that you are calling exports.author() first, which will return a reference to the inner function that is the middleware you actually want to invoke.

As other comments have noted, there might be better patterns for this type of access control that you could move toward in the future, but if your goal is simply to re-use the middleware you've already defined in different contexts I think this seems workable.

like image 200
Myk Willis Avatar answered Nov 11 '22 04:11

Myk Willis