I'll try to keep this as framework-independent as possible, but I will refer to the frameworks I'm personally using to give some context. Also, I'm sorry, but this is a long question.
Context: I'm working on an online platform. It is made of two different servers, a "backend" which serves a public REST(ish) API (at, let say api.example.com), and a "frontend" using Sapper (Svelte) with ssr (example.com). The latter basically serves an API client.
Obviously, some endpoints are related to a specific account and therefore need authentication. As far as I know, there are two different approaches to this, which have different vulnerabilities:
- session authentication (cookie), which is vulnerable to CSRF attacks
- token authentication (header), which is vulnerable to XSS attacks (usually the token is saved in
localStorage
)
Using session authentication directly with the backend server is quite difficult because the page is served by another server, and generating/verifying tokens is not that easy, if not impossible.
Common solution:
People seem to have real angst with using localStorage
, and I get it because an attacker can use XSS to steal the token and impersonate a user. Therefore the preferred solution seems to be the following:
- store the token in an httpOnly cookie
- every request goes to example.com/api/[...]
- these endpoints act as a "proxy", take the token from the cookie and put it into an Authorization header to forward the request to the actual API at api.example.com
The thing is, since authentication is now made with cookies, (at least at the "frontend" level), CSRF attacks are a problem again. Of course, now it's easier to deal with it because it's all a responsibility of the frontend server, and there are already existing CSRF middlewares for express and polka.
However, my point is that CSRF tokens and CSRF protection, in general, are useless in the presence of XSS vulnerabilities, and therefore, even if the token is not directly accessible by JS, the platform is still vulnerable. XSS protection is still critical, and we gained nothing by doing this. On the other hand, now the frontend server is "busy" for every backend request, and it destroys one of the advantages of having different servers.
What I was thinking of doing is basically sticking to the localStorage
approach, but without actually using localStorage
, in order to take advantage of ssr. The idea is that I can use the Sapper middleware to read the cookie and store the token in the Sapper session:
sapper.middleware({
session: (req, res) => ({
authorization: getCookie(req, 'supersecret') // get supersecret cookie from request
})
})
For those of you who don't know Sapper, session
in this context is basically a piece of memory that is initialized by the server and shared with the client. Making the cookie httpOnly doesn't really serve a purpose, because the value is still accessible by JS using the session store. But since the server has now access to the token during page reload, we can now take advantage of ssr (how useful is to render on the server private info got from an API is debatable, but why not?).
The question is: this is obviously not a standard approach, and therefore I don't understand the problems it may cause (or already has), as I can't seem to find any downside to this. What am I thinking/doing wrong? Is this actually ok to do?
P.S.: the "frontend proxy" is mandatory if I want to use API keys and limit the API usage, but I don't think I really need it (correct me if I'm wrong)