Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

What security issues does server rendering solve? I resent that every website needs to be an SPA, but from a security perspective I’ve concluded that the clearer line in the sand that SPA application architectures creates is better than the security challenges that can result from server side rendering.

Navigating the risks around the NPM supply chain is another story, but I suspect it will be solved by large / popular frameworks gradually pruning their own dependency trees resulting from downstream pressure in the form of pull requests.



Here's one I've experienced. Suppose you have a table of customers, and you want to show an extra column of data on that page showing total orders, if and only if the viewer of that table has the manager role.

With an SPA, you'll be building an API (perhaps a 'REST' one, or GraphQL) to expose all the data required, such as customer name, email, etc, as well as the extra 'total orders' field iff they have the manager role. Now you need to make sure that either (a) that endpoint properly excludes that 'total orders' field if they lack the permission, or (b) you have a separate endpoint for fetching those kind of stats and lock that behind the role, or (c) (I hope not) you just fetch all orders then count them up! Now, having properly secured your API so that all and only the right users can get that data, you have extra logic in your front end such as 'if has role manager then show this column too'.

With a server side rendered page, you don't have to do the API security bit. You can just fetch all the data you like (often via a single SQL query), and then replicate the same extra logic of 'if has role manager then show this column too'. You've skipped the whole "make sure the API is secure" step.

Now suppose you want to add a dashboard for every admin user, not just managers, showing total orders system wide. Did you include that in your API already? Now you're likely going to need a new endpoint that allows any such user to fetch total system orders, while managers are still the only ones who can fetch per customer order totals. Having found a place to add that to your API, you can render it. With the server side page, there's no extra work to keep this secure. The dashboard is already restricted to the users who can see it, so just add the extra query to fetch total orders and display it.

In short, there's a whole layer that an SPA needs to secure for which there is no analogue with a server side rendered site. In an SPA you secure the API and then have the logic for what to show to whom. For the server side rendered site, you just have the logic for what to show to whom and that gives you the security for free.


Yes yes yes. You're not going to get much appreciation of this from newer devs. They've only known one thing. I shudder at all the human-hours spent on duplicative tasks related to the artificial frontend/backend separation.


As a slight counterpoint to that scenario, the front-end can often just get away with just checking whether the data exists in the response, rather than checking roles. This isn't quite as simple as the SSR alternative, but it at least removes most of the hassle with making sure the permissions match on server and client.

However, this doesn't help much when the restricted data is on a separate endpoint, since the app needs to decide whether to make the request in the first place.


Can't the backend decide which fields to send back in a JSON, the same way it would decide which HTML columns it would've rendered?

The front-end can pass a user role with every request and render whatever the backend sends it into a table.


manager role column

Tbh, I’d prefer a runtime which would be itself aware of such metadata, knew the role out of a request/session context and could build a ui table based on all-columns-ever template, from a query automatically built for this specific case, because querying and throwing away totals may be expensive. This manual “do here, do there” is a sign of a poor platform (not that we have a better one) and code-driven rather than data-driven access control in it. Painting walls and installing doors every time you want a meeting should not be a part of a business logic, regardless of which-end and its implications.


Yep, I think there's wisdom in what you say here for good design. My point is (using a very simple example) that there are ways in which server side rendering offers some immediate security benefits that don't automatically come for an API+front end design.

I'm not sure about its performance, as I haven't done a great deal of testing, but another tool to achieve some of what you suggest (assuming I've understood you) is using RLS. E.g., using the obvious query, and relying on RLS rules to return only permitted data. You can similarly send the appropriate role with the query [1].

I also note with interest that Postgres 15 includes some improvements that might make this kind of approach even more viable:

"PostgreSQL 15 lets users create views that query data using the permissions of the caller, not the view creator. This option, called security_invoker, adds an additional layer of protection to ensure that view callers have the correct permissions for working with the underlying data."

[1] https://news.ycombinator.com/item?id=30706295


Yes. It's a poor design. The UI shouldn't care about permission or visibility rules. Instead It should only take data and render tables based on their data+metadata. All the permission logic should be done in the API level based on the caller ID/context.


I do case (a) like this with SPA. Call to /orders (or any endpoint) will check roles/permissions and runs the proper query. A normal user will get [{name, email}], but a manager will get [{name, email, num_orders}]. A normal user call will not have the COUNT(*) from orders part anyway in SQL query (which is expensive). Only a manager's call will have that part. Most likely number of managers will be less than users, so the users get faster results. The results are returned to front end and if the {num_orders} column exists, it is rendered. Front end doesnt have to bother with any access control logic.

For the server-side rendered page, what seems to me is you are running same query for normal users and managers, which is fine too, but removing that num_orders column.

Ultimately in both cases, the access control logic happens on the server, and frontend don't have complicated access control logic anyway. My point is, with SPA also we can get the same server-side benefits, atleast in this case. Or am I missing something?


If you have non-html native mobile applications, you’ll need to have that complexity regardless of the techniques used to construct web page.


Yep, that's absolutely right. From my own experience (which is really quite small for that kind of thing), I tend to think that the API you want and would build for a mobile app is not going to be the same as the one you would build for your SPA site. But yes, when you build that API, you'll need that complexity.

I've been doing some hobby experimenting with doing this in a more generic way using Postgres RLS and putting a lot of that permissions checks into the database. That way, if I used PostgREST or my own solution for a mobile app API, the security rules should apply just the same to the API as they do to the website.


What do the tests look like for this? How do you check that you're not exposing the sensitive data?


I think anywhere you introduce more complexity, more ways for things to interact, it's inherently less secure without the additional work checking for both the App + the API being secure on their own.


There's no such thing as a secure "app". Only the API needs to be secure. That's more straightforward when your API looks like REST/RPC calls rather than "renders html templates to a string".


> That's more straightforward when your API looks like REST/RPC calls rather than "renders html templates to a string".

How so? You're now dealing with two applications (or two parts of an application) that need to understand and access authentication as defined by "the app"

If the same codebase handles auth across the board it's much simpler and more reliable.


From a security perspective, the client is 100% irrelevant. You might prefer to offer a good UX in the face of authorization failure, but that doesn't affect the security of your app one way or another.

Good APIs look like simple functions; they take certain very structured inputs (path, query params, headers, body) and produce a simple structured output (usually a json blob). They're usually well defined, limited in scope, often idempotent, and easy to write automated tests for.

HTML endpoints are more complex because they generally combine many different functions at once, rely on server side state like sessions, and generate large quantities of unstructured input. They tend to be hard to test exhaustively and it can be hard to reason about all the possible edge cases.


The operative word being good, which most APIs unfortunately aren't. You could make the exact same argument about APIs that you made about HTML endpoints, and vice versa. The problem, imo, is that writing a fronted is a lot harder for many people than writing a backend and they tend to spend more time on the front-end.

Security is hard, especially when most developers are ignorant or negligent about basic best practices. If I had a nickel for every website I've found that only has client-side validation I'd be rich.


Maybe it helps if you think of it this way:

An application can be very imperfectly thought of as having two bodies of logic, frontend logic and backend logic. In an SPA, if you secure the backend logic you are safe no matter what mistakes are made in the frontend. When rendering server-side HTML, the frontend logic and backend logic both run in a privileged security context. The attack surface area is larger.


> In an SPA, if you secure the backend logic you are safe no matter what mistakes are made in the frontend.

If. The problem I've observed is that people treat the backend as a dumb pipe for data and focus entirely on the frontend.

> When rendering server-side HTML, the frontend logic and backend logic both run in a privileged security context.

This isn't necessarily a bad thing. Business logic happening in a protected and opaque context means it isn't exposed and easy to reverse engineer or manipulate. An extremely common vulnerability on SPAs is "get a list of all the $STUFF user is allowed to see from endpoint A, then get all the $STUFF from endpoint B and filter out all the results they shouldn't see" because everything is still visible to the client; that exact same (suboptimal) logic is inherently more secure on the server. Another common one being "are we logged in or should we redirect?" Conversely, rendering content on the server makes it a lot easier to prevent certain vulnerabilities like CSRF.

That's not to say that I think SPAs are bad and AJAX is good, I just find the argument that SPAs are more secure if you secure the backend dubious. A SPA with an insecure backend can be just as insecure as a backend rendering HTML because the weak-point is the backend itself.

Edit: You could perhaps argue that SPAs are indirectly better from a security perspectiv because text serialization is safer than binary serialization. Though any serialization is still a potential weakness.


> The problem, imo, is that writing a fronted is a lot harder for many people than writing a backend and they tend to spend more time on the front-end.

The question then is whether the API is more part of the frontend or the backend.

If your backends are relatively easy and small, I think you should try to keep your APIs in that space and e.g. return JSON from a simple REST API with endpoint-level security.

On the other hand, if an API threatens to bloat and complicate your backend, use an API framework like Postgraphile or Hasura that gives you the tools to build powerful and secure APIs by writing some simple code or even no code at all.


> Only the API needs to be secure.

If users type passwords, sensitive data, anything into the frontend then any javascript, plugins etc pulled in by that page is an attack vector.


Unless you do something extremely silly with the login page, like sending it as a GET parameter, or storing it locally, or not having a CSRF token, or not using HTTPS, I don't see what special measures are required!


Sensitive data is not restricted to logins. If you are pulling in third party JS like for analytics, tracking, social, whatever then that is an attack vector. Marketing and business teams aren't responsible for security but they have the muscle to pull in dangerous code to the frontend that can be swapped out. It is naive to think that the API is the only thing to focus on.


The likelihood of a vulnerability in serverside logic is far higher and more impactful than a large marketing player like google analytics stealing PII.


If POST content type is application/json and is enforced, csrf is not possible. You cannot csrf put and delete unless you modify cors policies, all of which are server side vulnerabilities and not related to you using SPA vs server side rendering.


You can import malicious client side plugins irrespective of if it’s an SPA or server rendered. I’d much rather a silly plug-in be limited to client side JavaScript (which is sandboxed) over server side logic, which is not.


It's more straight forward when you have multiple kinds of frontends (iOS, Android, Web app), but if it's just a Web App it's less complex to just return HTML.

The default today to make an API is because we assume it'll have multiple frontends or because we want to make it easier to change frontends. That does not make it more secure; it's just a trade off we, as an industry, have made to deal with the reality of delivering apps/services to users.


>" think anywhere you introduce more complexity"

When your website is the actual complex application SPA makes very much sense and is actually less complex. And no you do not have to download all of it into a browser. Load parts replacing some inner html with the other and scripts on on need basis. Works like a charm. I am using couple of JS libs but no framework and there is no need to "build".

As for security - JS app talks to my own C++ backend using some JSON based RPC so I just have to validate the API only which is way less work as well.





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: