Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passport JS admin user verification

I have a Node, Express app with EJS at the front end.

I have this middleware function with passport that runs before all create, edit, delete routes.

function isLoggedIn(req, res, next) {
    if (req.isAuthenticated()) {
        if (req.user._id != "12345") {
            res.redirect("/error");
        }
        return next();
    }
    res.redirect("/error");
}

The best I could come up with to verify that my admin user is the one trying to access the route is to check by user id in mongo db with req.user._id

Is there a better way to handle admin user access to routes and html components?

like image 954
Ian Avatar asked Apr 08 '26 17:04

Ian


1 Answers

This looks essentially correct to me. It is important to note there are two layers: authentication and authorization.

Authentication is effectively a boolean: is the user authenticated? You have your function there req.isAuthenticated(). This might logically return a boolean, true or false, for whether or not the user is authenticated (ie: logged in).

Authorization could perhaps take many forms, but is effectively again a boolean: does this user meet the criteria to access this resource.

Authentication is usually well-served in a middleware, somewhere central that runs before "the endpoint", but authorization is not as simple because any endpoint could allow an operation or deny it, or it could respond differently depending on the user's privilege.

This whole conversation is perhaps quite deep in a roles & permissions discussion.

I think the answer is dependant on the app. Your app has two requirements: one, the user must be authenticated, and two, the user might need to be an admin.

The answer will be somewhere around: what is the most simple way to accomplish this?

In my opinion, you would consider the SOLID principles and note that you have one middleware, so it should have one responsibility: to check if the user is authenticated. Next, maybe you should have another middleware called isAdmin that runs for every endpoint that requires this extra condition. That's really all it is--an extra check. You shouldn't pollute your isLoggedIn middleware with that extra stuff because it makes that middleware less reuseable and less composable.

An isAdmin middleware would be a good idea, but it could also be a good idea to simply have it as a function inside every endpoint that requires that admin-check. Which way is better? Well first, which way is simpler. Which is less code but that is also still simple to understand.

Because this is roles and permissions, is there maybe a more robust way to keep track of which users are admins? If you have code that runs like if (req.user._id === 12345) {}, it requires special knowledge to remember this place in the code, so it is kind of brittle and "more likely" to fail. Maybe it would be a good idea to add a column to your users table for is_admin which could be null or 0 for every user except your user which could have 1. Then you could check if (req.user.is_admin) {}.

That might lead us to a middleware function like:

function isAdmin(req, res, next) {
    if (req.isAuthenticated() && (req.user.is_admin === 1)) {
        return next();
    }
    return res.redirect(403, "/error");
}

You could also do something like change that is_admin database column to instead something like role which could be 1 for every user except your admin users which could have maybe 2. That would allow you to do something like:

function hasAuthorization(req, res, next) {
    if (req.isAuthenticated() && (req.user.role >= 2)) {
        return next();
    }
    return res.redirect(403, "/error");
}

Such logic there can allow you to have increasing privilege roles: maybe 1 is regular, 2 is manager, 3 is admin, 4 is super-admin. If the user's role is less than 4, they don't have permission.

In my opinion, this idea of increasing privilege is great except the critical flaw might come later when you refactor your routes or your roles. You'd have to remember everywhere you had > 3 and change it to > 4. If you forget any, that is kind of a security flaw immediately, so I trust you understand my argument there.

Rather than seeing operators like < and >. I would rather see checks for specific roles, like:

if ((req.user.role === 'ADMIN') || (req.user.role === 'MANAGER')) {}

We have to keep coming back to the idea: what is the most simple? Is it simpler to make an isAdmin middleware and then group all your admin routes under the middleware? or is it simpler to put the authorization-check inside each route?

Check this example here:

import isAdmin from '../auth/isAdmin.js'

app.get('/admin', (req, res) => {
    if (!isAdmin(req.user)) {
        return res.redirect(403, '/error')
    }

    return res.render('admin')
})

This might be more work, but it's also potentially more fine-grained, so you have more control.

app.get('/foobars', (req, res) => {
    if (isAdmin(req.user)) {
        return res.json(/* all foobar records from all accounts */)
    }

    if (isManager(req.user)) {
        return res.json(/* all foobar records from the user's account */)
    }

    return res.json({ error: 'Insufficient privileges for this operation' })
})

My final thought is that, you should have two functions: one checks if the user is authenticated, and one checks if the user is authorized. Then you can stack them together either in a middleware or in two middlewares, or in a route.

I also think you should find a more robust way to check if the user is your self. If you move your app from one computer to another, the user ID might change next time you populate your users table, so id isn't a strong way to latch onto your user.

like image 51
agm1984 Avatar answered Apr 11 '26 10:04

agm1984



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!