I'm currently working on a MVC app. I've implemented a route using a POST method to retrieve information and navigate to the corresponding page, and I have some concerns about the design practices I'm using. This is the route:
app.post('/metrics/service/:serviceId', async (req, res) => {
target = req.query.target;
errors = [];
if (!target) {
res.render('metrics', {error: 'Please select a target to get the metrics from'});
}else{
await MetricsService.getServiceDetails(req.params.serviceId, target, req.body.token).then((data) => {
const mermaid_diagram = MetricsService.getMermaidServiceDiagram(data);
res.render('metrics_service_details', {target: target, mermaid_diagram, data, cnt: 0, errors: errors})
}).catch((error) => {
res.status(500).send('Error: ' + error);
});
}
});
I have several concerns regarding this implementation:
Use of POST for retrieval: Is it a good practice to use a POST method to retrieve data? Should I consider changing it to a GET method instead?
Handling tokens in the URL: The token required for authentication is passed as part of the request body (req.body.token). If I change the method to GET I'm concerned about security by having tokens in the URL. What would be a more secure approach? Is it better to pass tokens via headers or any other recommended method?
Combination of URL parameters and body data: Currently, I'm using a combination of URL parameters (serviceId), query parameters (target) and the token from the request body. Is this approach acceptable, or should I modify it to maintain a more consistent approach?
I tried a couple of approaches, they all work as intended but my concerns are about best practices.
Use of POST for retrieval: Is it a good practice to use a POST method to retrieve data? Should I consider changing it to a GET method instead?
Unless you are under specific constraints (REST comes to mind), the main goal is overall uniformity of the component interface (of the API), and using POST for retrieving data when you have reasons to do so is just fine: for one thing, it is what we are compelled to do if we need to pass a body with the request, because in theory a body can also be attached to a GET request, but frameworks may not honour this: I am not sure about Express.js, but e.g. ASP.NET MVC does not (at least the last time I have checked, around a year ago), namely, the body the controller method (the request handler) receives is always empty for GET requests.
Using POST is not equivalent to using GET though, the main implications I can think of being:
the response to a POST request is not cached unless you also use appropriate response headers, and even in that case the situation is similar to using a body in a GET request, that browsers (to today) may not actually honour the caching headers, so better not rely on that at all: see this thread for more details: Is it possible to cache POST methods in HTTP?
reloading the result page from a browser incurs the "reposting the request" warning, which is bad user experience (most users don't even know what is going on), plus it may be quite problematic if the request handler (the controller method) does actual work instead of just retrieving data, in which case a scheme for idempotency would be needed (e.g. not to submit the same order twice just because the user hit reload and confirmed).
Those issues considered, a best practice where it makes sense, i.e. where users and their experience is in fact involved as opposed to programmatic access (an API in the strict sense), is to rather respond with a redirect from the POST handler to a plain GET resource.
Handling tokens in the URL: The token required for authentication is passed as part of the request body (req.body.token). If I change the method to GET I'm concerned about security by having tokens in the URL. What would be a more secure approach? Is it better to pass tokens via headers or any other recommended method?
For pages served under plain HTTP, there is no difference between putting sensitive data in the headers of the request vs in the query string vs in the body, and the most common if not canonical for tokens and similar is to use headers: just do not put it in the address itself, i.e. as a component of the URL path, as the URL path is not encrypted should you upgrade to HTTPS. I say no difference in the sense that all that is easily accessible to any user who can at least use tools like PostMan or a network sniffer.
Be also aware that under plain HTTP the query string (as everything else) is not encrypted, and not just the server but also browsers and intervening proxies may log and cache the resource under the whole URL including the query string.
Of course, the best practice here is to use HTTPS in any case, although with the caution that HTTPS encryption itself is not terribly hard to break, which is the reason for more robust authentication workflows such as JWT (which I'd quite recommend), where at least the attack surface is minimized: one authentication event, then a clever scheme for just passing around the equivalent of a symmetrically encrypted and usually temporary token. (I am not a cryptography expert, I hope I am not simplifying too much here.)
Combination of URL parameters and body data: Currently, I'm using a combination of URL parameters (serviceId), query parameters (target) and the token from the request body. Is this approach acceptable, or should I modify it to maintain a more consistent approach?
I think it is perfectly legitimate, the main indication here is "make it make sense", to you who are writing it (ideally as reflected by some specification on top), as well as to users of those methods and of your component interface overall. In your example, I'd read it that the resource (its "identity") is the whole URL together with the serviceId, while the body of the request is about what I want to do and/or be done with it.
I'd greatly appreciate insights and best practices on how to architect this MVC route for efficient and secure data retrieval.
Efficiency as in performance is quite independent of all of the above: anyway, as far as I can tell (short of inspecting framework source code), there is no significant difference between pre-processing a POST request by the framework (Express.js or else) vs pre-processing a GET request.