Node.js: Authorizing routes vs. authorizing methods - node.js

Quick background
I am building an API with Node.js, Express and Mongoose. The authentication I implemented works with the passport-headerapikey package. I search the DB for the user with the api-key and add that user to the req Object. Thus ensuring my knowledge about the identity the whole time until the request ends.
Authorization
Let's get to the issue.
Up until now I called an authorize() function in every endpoint manually before doing anything. Like so:
router.post('/', (req, res) => {
autorize('admin', req.user.role) // method is called at every route manually
.then(() => {
... do stuff here
})
.catch(err => req.status(403).send())
}
My colleague said to me it is not a good solution and, rather than just securing the endpoint itself, I should have a session management that makes the current user available globally so that I can authorize at any point in every function individually.
Meaning:
A Method createUser(obj) could then call inside itself an authorization method or check for a condition like so:
createUser(obj) {
if (currentUser.role !== 'admin') {
return false
}
obj = new User(obj)
return obj.save()
}
That would mean I would return false in every function if a condition is met. Accessing the globally available currentUser for that session. (e.g. globalCurrentUser.role !== admin or something along those lines)
My question(s)
Is it bad practice to just secure endpoints and not functions?
Can't I just require an extra param "auth" with every function, so that when called it needs to receive the currentUser.role like in my authorize() function or it returns false? That means I pass the user manually to every function or it will simply fail
If I should have a session management for global access of the user during the request: Do you have suggestions for frameworks?
Thanks in advance,
Benno

Authentication and authorisation are two different things and should be treated separately. Authentication says "who are you?" and authorisation says "do you have permission?". Baring in mind that Express is designed entirely around middleware, I would do the following.
Abstract your authentication into a single piece of middleware which runs for all your endpoints using app.use() before you mount your router / routes.
Create an authorisation function which can be called from anywhere, it takes a user (or id or whatever you have) and a role, and it then checks if the user has that role.
Think of it like this, your authorisation will never change, it is core to your application. If you decided to ditch Expressjs and use Koa or move from traditional HTTP requests to Web Sockets you wouldn't want to change your authorisation logic. But your authentication may well change, you may wish to no longer use a header to store the token, perhaps you switch to cookies or something else entirely, you may wish to change this.
You'll end up with a piece of global middlware which checks an auth token and attaches the user object to the req. Then you'll have a utility function called something like userHasRole which will be called at any endpoint which requires a specific role within the application. You're then free to check permissions at any point in the application. This may be in very different places across your application, for instance you might check if they're an admin at the beginning of a request to some admin dashboard, but you might check permissions later on if they try to access a particular resource. When accessing a particular resource you might want to let them through and determine at the last minute if they have access to the resource. (It's hard to give a specific example without knowing more about your application).
In some instances it might be suitable to check at the beginning of the business logic, in other places it might make sense to check later on. This shouldn't matter, you should be able to run this check whenever you need to. This will depend entirely on the business logic and placing it in every single function ever may be useless if it's just formatting a string output, but it might be useful when trying to pull out a DB record.

Related

Issues with new express-openid-connect package

I have been trying to use express-openid-connect for the last few days with no success. I am able to get the flow to work when hard coding my params. However, my goal is to be able to dynamically call auth() depending on the user being logged in. My initial attempt included calling
app.use(auth(functionThatGetsParams()));
Using the approach above, express complains that secret is required. For some reason, the auth call is getting called before anything else is resolved.
I also tried doing a few different ways,
app.use((req,res, next)=> process.env.secret = 'hello');
app.use(auth({secret: process.env.secret}));
The example above also returns the secret issue. However, setting process.env.secret outside of app.use, works fine.
My issue seems to be related to the things I do in the app.use block. The approach I am looking to use is have a call that resolves who my user is and based off of that gets the right settings.
app.use(ConnectionResolver.resolve);
I use a call similar to the above which is basically a handler that does some async stuff to get the client info and right settings then ends with next().
I would expect that then calling
app.use(auth(ConnectionManager.getAuthSettings()));
Would return the auth settings I need, but when I debug, it looks like this line gets called before anything else, so then secret is missing as the error says.
One other option I believe I may have seen online is creating a list of auth calls for each client, which I can then use for authentication, but I have not seen any examples of how that works.
Does anyone have any ideas on how this might be possible? The environment I am in is multi tenant. So I need to be able to dynamically use a certain auth config depending on the user making the call.
Any help would be greatly appreciated.
You are misunderstanding the concept of middleware.
the auth function, is a middleware factory function, it gets a set of options and returns a middleware function based on those options.
The function passed to the use method of the express app, will execute only when an incoming request will arrive.
When you do app.use(auth(getParams())) what happens is that when your server is starting, it will call getParams function, pass the result to auth function which in turn will return the auth middleware function that will be passed to the app.use function.
Once a request will arrive, the auth middleware (the one returned by the auth factory function) will execute.
You don't need to use auth conditionally. You should set it up, and then you can use the requiresAuth middleware provided by express-openid-connect package to protect your paths that requires authorization/authentication.
If your secret is loading asynchronically, wrap your entire express app setup in a bootstrap function, load your secret and only then call the server bootstrap function.
async function loadSecret() {
//load secret from external source
}
function bootstrapServer(secret) {
const app = express()
app.use(auth({ ..., secert }))
app.get('protected', requiresAuth(), (req, res) => {
// your protected route, will automatically return 401 if not authenticated
})
app.get('non-protected', (req, res) => {
// This route will be open to all without authentication
})
}

How to implement JWT auth on Loopback REST API?

I´m pretty new at nodejs, so I tried to implement an REST API with the loopback framework to try to simplify a bit the building process. I did correctly the models, also cusomized some endpoints, but, when connecting with the frontend (AngularJS), all the code I find, also the code I know to build, requires an JWT token to do any task that requires authorization/authentication, but it seems that loopback sends an uid2 token when I log in. I searched a lot, in stackoverflow and Github, the nearest thing I found is this (https://github.com/strongloop/loopback/issues/1905#issuecomment-310382322) but the code seems to fail here:
const data = jwt.verify(id, secretKey);
cb(null, {userId: data.id});
Any idea?
In simple words, you need to know who is making the call. If you'd use the default loopback authentication you would see that in the req object there is accessToken property which identifies the user. If you want to use JWT there are plenty of ready modules you could use, eg. loopback-jwt or loopback-jwt-advanced.
In general, what you need to do is to apply a middleware that will be responsible for authorization of your user( I strongly recommend you to get familiar with the middleware term). In simple words, middleware is a layer that your requests are going through and you can modify it's a body or reject the request.
In abstract steps, in your middleware you would have to:
get the token from the request
verify the token
identify the user based on the token
create the loopback AccessToken entity for that given user
put the token in the req.accessToken so now loopback will know who you are and you could use the ACL.
So this is more or less what those extensions are doing.

Best way to handle API calls from frontend

Okay, so atm i have a frontend application built with Nuxt JS using Axios to do requests to my REST API(separate).
If a user does a search on the website the API URL is visible in XMLHttprequests so everyone could use the API if they want to.
What is the best way of making it so that only users that search through my website gets access to the API and people that just directly to the URL gets denied. I suppose using some sort of token system, but what is the best way to do it? JWT? (Users never log in so there is no "authentication")
Thanks!
IMO, you CANNOT block other illegal clients accessing your
backend as you describe that the official client and other illegal have the same knowledge about your backend.
But you can make it harder for illegal clients to accessing your backend through some approach such as POST all requests, special keys in header, 30-minutes-changed token in header and server-side API throttling by client IP.
If the security of the search API is really important, authenticate it by login; if not, just let it go since it is not in your critical path. Let's focus on other important things.
I'm in the same "boat" and my current setup is actually in VueJs but before even come to StackOverflow I developed a way to actually, the frontend calls the server and then the server calls the API, so in the browser, you will only see calls to the server layer that, the only constraint is that the call must come from the same hostname.
backend is handled with expressJs and frontend with VueJs
// protect /api calls to only be originated from 'process.env.API_ALLOW_HOST'
app.use(api.allowOnlySameDomainRequests());
...
const allowHostname = process.env.API_ALLOW_HOST ||'localhost';
exports.api = {
...
allowOnlySameDomainRequests: (req, res, next) => {
if(req.url.startsWith('/api') && req.hostname === allowHostname) {
// an /api call, only if request is the same
return next();
} else if (!req.url.startsWith('/api')) {
// not an /api call
return next();
}
return res.redirect('/error?code=401');
},
...
};
In our case, we use Oauth2 (Google sign through passportJs) to log in the user, I always have a user id that was given by the OAuth2 successful redirect and that user id is passed to the API in a header, together with the apikey... in the server I check for that userid permissions and I allow or not the action to be executed.
But even I was trying to find something better. I've seen several javascript frontend apps using calls to their backend but they use Bearer tokens.
As a curious user, you would see the paths to all the API and how they are composed, but in my case, you only see calls to the expressJs backend, and only there I forward to the real API... I don't know if that's just "more work", but seemed a bit more "secure" to approach the problem this way.

Using Global.var in NodeJS to temporarily store redirect URL to use after OAuth callback

I have a nodejs app (http://app.winetracker.co) and I'm integrating OAuth logins. I think I can use a global.redirectURL var to temporarily store the redirect URL for use after the OAuth callback.
// url param passed to route /auth/twitter?redirectUrl=/path/to/location
app.get('/auth/twitter', function(req, res) {
var redirectUrl = req.param('redirectUrl');
global.redirectUrl = req.param('redirectUrl');
passport.authenticate('twitter', {})(req, res);
});
app.route('/auth/twitter/callback').get(users.oauthCallback('twitter'));
If I have 2 users logging into my app via OAuth at the same time, will the global.redirectURL values get overwritten by each user's redirect var value?
Essentially, are global values unique to each user or does everyone share the same global.redirectUrl var value?
If I have 2 users logging into my app via OAuth at the same time, will
the global.redirectURL values get overwritten by each user's redirect
var value?
Yes, they will get overwritten and doing it this way is a serious problem.
Essentially, are global values unique to each user or does everyone
share the same global.redirectUrl var value?
Global values are shared with your entire server so storing anything there is visible by ALL requests by all users that might be processing. You should pretty much never store this kind of temporary information in a global. If the auth code is async (which I assume it is), then you can easily have multiple requests trouncing/conflicting with that global. This is a bug ridden thing to do. You must change to a different way of solving the issue.
The usual solutions to this type of issue that do not have the vulnerability of a global include the following:
Use a session manager and place the data into the session for this particular browser so it can be retrieved from there during the redirect.
Put the information as a query parameter on the redirect URL so when the browser comes back with the redirect URL, you can parse it out of the query string then.
Coin a unique ID for this request, set it as a cookie and store the temporary data in a map using the ID as the key. Then, when the redirect comes back, you can use the cookie to get the ID and then lookup the value in the map. This is essentially a session, but it's special purpose just for this purpose. To be thorough, you also have to make sure your map doens't leak and build up over time so you have to probably store a timestamp and regular clean up the map by removing old values.

How to access a global variable on client and server in node/express?

Let's say I have a variable, x, which is set on the request req.x in an express middleware. We can expose this to client through the template, <%- window.x = req.x %>.
I would now like to use this variable globally. On client, we can use x directly since it's in the window context. But on the server, how do we do the same if there is no window or global context?
We can't set it on globals object in Node because that really global, not global to the request.
There is no such thing as global to a request. Because there can be many requests in flight at once in node.js, there is no place to store something that is global to a request other than on the request object itself.
So, the usual solution here (and the general OO way of doing things) is to just pass the request object to anything that needs request-specific state. Then those functions can access whatever state is desired on the request object.
You can manufacture some sort of global store lookup that would allow you to store something globally and then be able to look it up given some key (such as a userID for a given request or some cookie value for a given request). This is basically how persistent session data is stored. But, if you're trying to access data that is specific to a user or to a request, you're going to have to pass something along to any function that needs that info that can be used as a key to look up the right data in the global store because there can be many requests active at a time.
While node.js is largely single threaded, any time you make any sort of async call in a request handler (reading asynchronously from the disk or doing networking or setting a timer), then other requests get a chance to start running and many can be in flight at the same time. Thus, there is no way to associate generic global state with any given request.
The usual way to solve this is to just pass this state along to other functions that might need it:
app.get("somepath", function(req, res) {
callSomeFunc(req);
});
function callSomeFunc(request) {
callSomeOtherFunc(request);
}
function callSomeOtherFunc(r) {
// access request info from the r argument here
}

Resources