4

I would like to hide certain content behind a paywall in my VueJS app. In googling around...

  • Some tutorials recommend storing the currently logged in user in local storage. And then before showing the restricted content, you can check to see if the currently logged in user has permission, eg. localStorage.user.role === 'subscribed'. But local storage is accessible to users, so they could easily just run something like localStorage.user.role = 'subscribed', or just edit local storage directly in the dev tools. (The same thing applies for other forms of web storage from what I understand.)
  • Other tutorials recommend storing the currently logged in user in Vuex, which is basically just Vue's data model and is conceptually the same thing as storing it in memory as JavaScript, from what I understand. Vuex in particular can be edited my a malicious user, and so can JavaScript more generally.
  • I suppose a third option is checking the users cookies to see if they have the cookie set for a logged in user, but cookies too can obviously be altered by a malicious user.

So, my question is whether it is possible to truly hide content behind a paywall in a single page app. And if so, how?

2
  • yes, using a server to decide what to send/not send to a given known user.
    – dandavis
    Commented Apr 29, 2019 at 20:58
  • This seems to be more related to programming or software design, rather than information security. How exactly is this question about information security?
    – user163495
    Commented May 29, 2019 at 9:01

4 Answers 4

1

Access control needs to be done server side, not client side. The moment you send the data to the client, it is game over. It can now be read by the user, no matter what your SPA looks like. The content can still be taken from the HTTP requests, from JS variables, local storage, or wherever you put it.

You'll need to have a backend API that only serves the paywalled content to authenticated users. So there has to be a server side authentication mechanism. That is the only real solution.

1

You can have a SPA and still have the (real) content be protected.

ie.

Let's say that the paywall data is just text and images.

Publically you expose your standard static assets, ie. index.html, some Vue javascript, your CSS.

All of this handles the look-feel, user interaction with the website etc.

On the backend, you have an API serving up the protected content, and that requires authentication, whether that's cookies, or JWTs doesn't matter. The backend API will only give the data if the JWT or cookie is valid.

So the frontend attaches a cookie or JWT to the requests to this API, and that's how it gets and displays the protected content.

So the malicious user can hack at the javascript all they want, and see what the website might look like for a legitimate user, but they can't see the content itself.

Now note, they might be able to see the shape of the content. For example, if they read the frontend source code and see something like this:

    const response = await fetch("/api/users"); 
    const data = await response.json(); 
    const {ssn, address, id} = data; 

Then they know what kind of data you returning from your API.

But that's where you could obfuscate that data, like this:

const data = [
    {   
        key: "ssn", 
        value: 123, 
    }, 
    {
        key: "address", 
        value: "foo bar lane"
    }
]

and display it like:

   data.forEach(v=> console.log(`${v.key} : ${v.value}`));  

But of course, that might prove unwieldy, it's a question of whether a malicious attacker knowing the shape of your data is really problematic or not.

1
  • Thanks for the input! That's an interesting approach and I considered it, but for my app it would make the code really messy so I'd prefer to take a different approach. Commented Apr 29, 2019 at 19:28
0

I suppose a third option is checking the users cookies to see if they have the cookie set for a logged in user, but cookies too can obviously be altered by a malicious user.

Users can alter cookies, but it will be hard to acquire a valid logged in cookie. If the cookies are a session ID, the session state is maintained server side, and can not be manipulated.

If the session ID is reasonably secure (e.g. 128 bit random values or similar) they can't be brute forced either. In this case, the easiest way to acquire a valid cookie is probably to get it from a legitimate user. If the attacker has a way of acquiring cookies from a legitimate user, they can probably grab the password and username as well.

Web authentication is a fairly well known problem, with many solutions. The security level offered varies, but I'd say that for a low-value content (e.g. monetary value of 10-20$, but no real outside a possible loss of sale) session cookies is probably secure enough.

7
  • 1
    No, it doesn't matter how you store it. If it's stored on the users device, and transmitted to the server, it can be manipulated by the client. This is universally true. With HTTP, the client is free to send the server any data it wants. Limiting lifetime to e.g. 24 hours would make cookie reuse less interesting.
    – vidarlo
    Commented Apr 29, 2019 at 19:36
  • I'd like to have users with different roles, the main ones being paid and unpaid. I'd like to give authenticated access only to paid. I'm not quite seeing how a session ID would allow me to do that. The way I'm thinking about it, somewhere I'm going to have to check the role of the logged in user. The simple way is to store the user somewhere and check user.role, but that can be manipulated. I guess I could check if the session ID matches some pattern that is hard to brute force, but the logic for that could be discovered by inspecting the source code, and circumvented. Commented Apr 29, 2019 at 19:39
  • (For others' reference, I initially posted a comment asking if it matters how you store it, and whether I'd have to automatically log users out for the approach to be secure.) Commented Apr 29, 2019 at 19:45
  • Where does the paid content come from?
    – vidarlo
    Commented Apr 29, 2019 at 19:48
  • I want to have a freemium model where any user (not logged in, logged in and unpaid, or logged in and paid) can run eg. 5 simulations for free per day, and past that you need to pay. So basically the whole app is paid, except for pages like home, about and contact. Commented Apr 29, 2019 at 19:59
0

As you said it is a bad idea, Authorization validations have to done at backend, frontend validations are only for UX.

Could you please review solution described below with list items.

  • Use JWT as you session cookie
  • Save following data to this token IP, md5(User-Agent), User-GUID, Session-GUID
  • Store sessions at database and remove when user logged out. Check Session-GUID from JWT token when user try access to content.
  • Check other JWT parameters at every request (so no sql request will be needed)

Cookie enumeration prevention

  • Respond with HTTP 202 when a user try to access your website with a invalid/non-authorized cookie and send a new cookie (valid).
  • Deploy rate limiting rule to your WAF for generic request count, for example "if a ip address send more than 50 request in a minute (DOC request, not resource like CSS/JS/etc).
  • Deploy rate limiting rule to your WAF for HTTP 202, for example "if a ip address get HTTP 201 more than 5 times in a minute"

PS. cloudflare / imperva incapsula are most common cloud based WAF providers, both of them have rate limiting capabilities with their own customizations so please review them of feel free for asking more details.

4
  • Regarding your proposed solution, there's still the question of what to do when a user navigates to a route that should be protected. Given that we're talking about a SPA, the check would have to happen on the front end. But if it happens on the front end, but all the approaches I can think of that make the check on the front end are vulnerable. Commented May 29, 2019 at 17:41
  • I proposed loading all contents from backend with ajax requests. You can still hava single page app just write conditions for responses. That solution does not check authorization (payment status/access status/etc) at backend. If you are saying "there will be only frontend, no backend", longs story short you can not do this without backend.
    – alnbhclyn
    Commented May 29, 2019 at 19:22
  • Gotcha. That's sort of what dwjohnston proposed in his answer as well. Commented May 30, 2019 at 17:34
  • Exactly but not limited with. As like as @vidarlo mentioned about session security, without a proper authorization validation mechanism system will be compromised.
    – alnbhclyn
    Commented May 30, 2019 at 18:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .