1

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)

pianka
  • 142
  • 1
  • 1
  • 14
  • 1
    What is the benefit? Maybe it's just me, but it's unclear why you would be doing this. My initial thought is security-wise this is the same as storing it in something like localstorage from the client's perspective, but with additional dependencies and a bigger attack surface, more potential configuration errors and more complexity in general, which is not good for security. – Gabor Lengyel Feb 28 '21 at 21:36
  • @GaborLengyel using the cookie allows me to pass the authorization token to the server to retrieve information from the API and to use server-side rendering – pianka Mar 01 '21 at 13:27
  • How is this approach better than storing the token in localStorage and adding it to every request as a header? – Gabor Lengyel Mar 04 '21 at 23:53
  • Is it really necessary to be stateless here? I mean REST APIs are not exactly designed for this kind of client-server communication, they are more for 3rd party or internal clients. This approach is usually useful only if you (plan to) have multiple type of clients e.g. webpage, mobile application, desktop application, etc... possibly developed by other teams and or you have a lot of users and you don't want to manage sessions on the server. If this is ok, then the question can be simplified to "how to avoid xss and csrf by token auth". – inf3rno Mar 05 '21 at 08:41
  • It sounds like a question is more about [cookies vs local/session storage](https://stackoverflow.com/questions/3220660/local-storage-vs-cookies). Have flagged this as an opinion based one. – Aivaras Mar 05 '21 at 09:21
  • 1
    FYI look at how Auth0 is storing a token in the browser https://github.com/auth0/auth0-spa-js/blob/master/src/Auth0Client.ts – Aivaras Mar 05 '21 at 09:32
  • @GaborLengyel my "necessity" was just to improve user experience and, instead of making them wait for the page to fetch with a loading icon, have directly the paged served by the server. I understand it's not really necessary but it seemed nicer. – pianka Mar 05 '21 at 10:57
  • @inf3rno we don't want to exclude the possibility of having a mobile app in the future, even if at the moment it isn't really necessary – pianka Mar 05 '21 at 10:57
  • @Aivaras yes, while ultimately it is about that, I also wanted to understand if there are some specific faults in my weird mixed approach, hence the new post – pianka Mar 05 '21 at 10:58
  • 1
    @pianka Try asking it on https://security.stackexchange.com/ if it fails here. – inf3rno Mar 05 '21 at 13:32

3 Answers3

2

So what you really want to do is the following:

  1. doing stateless authentication and authorization to increase scalability

    You need to store the session data on the client. You need to send the relevant part of the session data with each request. You need to add a hard to guess secret to the session data that can be used to verify user identity. You need to check this secret on the server for each request.

  2. avoid session theft via XSS

    You need to move at least your secret to a HTTP-only cookie, so nobody can steal it with javascript code running on the client.

  3. avoid session usage via CSRF

    You need another hard to guess secret which can be used to verify user identity or which is unique for the actual session. This secret can be known by the javascript code running on the client and it must be sent with each request in a non-cookie way. You need to check this secret on the server for each request, but it is not necessary to generate it on the server if running client side javascript code is a requirement to use your application.

A possible way to implement these is moving the entire session to an encrypted signed JWT created and modified by the server and stored on the client in a HTTP-only cookie. You can add the user id, the session id and the expiration date to this session. You verify its signature for each request and you check if the session id is not on a blacklist or if the session is not expired. You renew the keys you use to encrypt and sign the JWT periodically e.g. each month. You send the encrypted session id in a HTTP header too and compare it to the one you get in the JWT for each request.

A less secure and from server perspective a less demanding implementation is storing only the user id, expiration date and session id in the token only signed but not encrypted. The rest of the sesssion can be stored in the localstorage or sessionstorage and it can be read and modified by the client not just by the server.

An even less secure way is sending only email and password with each request instead of signing stuff.

Be aware that this is just my opinion, not some industry standard and I recommend using an existing well tested product instead of doing your custom security solution.

inf3rno
  • 20,735
  • 9
  • 97
  • 171
  • This is the very standard approach, basically what you could do with PHP, but with tokens instead of session cookies. What is throwing me off is the fact that the backend is served separately and I wanted to use ssr with the frontend server... Either I want too much stuff or I'm missing something. (email and password is obviously out of the question) – pianka Mar 05 '21 at 21:19
  • 1
    @pianka I think email and password can be ok too, it really depends on what kind of security your application needs, how much damage a stolen account can cause. People usually don't realize, but cybersecurity is a different job than programming. If this is a serious application, then you'd better ask an expert to help design it from security perspective too. It is a lot easier to design it with good security than trying to fix it later. For example common criteria can help to design secure applications, but it takes time to learn. https://en.wikipedia.org/wiki/Common_Criteria – inf3rno Mar 05 '21 at 21:45
0

A LocalStorage (or MemoryStorage if you wish) is never a good solution for prod purposes. If you're just doing a demo or showcasing a change only then should you stick to a LocalStorage memory caching. Use Redis for a dedicated caching service.

It is upto you whether you wish to utilise a token based system or a session based system to ensure congruency between server-client. You could take in specific parameters in the request to ensure better identity (I.P. location, shorter expiry time, etc.)

Set up an https certificate to make XSS attacks to have lesser efficacy. Utilise server generated tokens for ensuring session based CSRF attacks are harder to perform ( generated token can be asymmetric or symmetric type).

Kaustubh J
  • 552
  • 5
  • 8
  • I was already thinking of keeping track of IP addresses to try detecting weird behaviors and hopefully spot stolen tokens to revoke them. With my current approach CSRF prevention is unnecessary, isn't it? – pianka Mar 05 '21 at 10:56
  • 1
    Older browsers even allowed you to capture the MAC address and Battery ID of a laptop. Keep searching for immutable kind of information that can be used for good identity classification. (Encrypt any token that you store as cookies. Use at-least a sha512 level of encryption) – Kaustubh J Mar 05 '21 at 11:06
-1

When in doubt look at how experts handle the issue (unless your approach is novel). Banks for example have tight security with short session lifespan therefor the user will have to login again every x seconds, so even if an attacker stole the first session key he will not have the new session after you re-authenticate (assuming that he doesn't have a contentious method of stealing the new session key) it adds a hurdle but is not the ultimate defence since that doesn't exist. Maybe you can also look at asymmetric encryption using private and public key pairs, or unique code generators that the front end must always generate a new pass-phrase based on an encryption key that both the frontend and the backend have and you have to send that every evolving cipher with every request, the attacker will not be able to get through this security measure unless he has access to the front end code, again not an ultimate defence but an extra hurdle for the attacker. A fancier approach is to use machine learning to identify typical user behaviour patterns and flag out of the ordinary activity, and send an alert/re-authenticate if confidence level is low in the current activity session.

Mohammed
  • 510
  • 5
  • 7