3

I am trying to implement a role based access control system with react-redux. So far I've been reading up on it and have seen some general methods along the lines of:

<View>
    {!user && <LoginScreen />}
    {user && <WelcomeScreen user={user} />}
</View>

which is basically using variables or the state to test conditions and grant access to certain components. My predicament occurs when I came across this post:

Can react state be tampered with to bypass security measures?

If the user can tamper the state and view the components where they don't have permissions, what is the proper way to ensure role based access? Storing the user data as position: employee could be changed to position: manager in the client, and then they would be able to see what options are available to managers, even if no REST requests have been made.

I am currently using session cookies to validate the user server side, and any data-sensitive REST get/posts are authenticated - but how does this translate to the client?

user logs in -> server verifies -> server sends back session variable that holds user.position / or sends json(user) -> client looks at session from server or json(user), stores user.position in state.user.position -> state.user.position is used in conditional if to determine whether or not to display components as above ---*but*---> client goes in and changes state state.user.position and gets access to components anyways

If we can change state in the client, how can we securely use conditional tests in react/redux?

chickadee
  • 301
  • 1
  • 2
  • 9
  • 2
    Never trust the Client! If an employee changes his/her role to manager, your client would try to get manager related data via an API call where the API then checks authorization and rejects. (as you said your REST api does) That means also that you don't load sensitive data beforehand and "hide" it. – Martin Trenker Jan 25 '19 at 20:46

1 Answers1

5

Something like this is handled by back-end, not by React. Whatever you do, DO NOT just grab all the data and filter it in React. I have no idea how you differentiate users based off of your example, but, for simplicity I'll assume you're using JWT. In this implementation, a token is generated when you login, and you can safely store this in eg local storage or redux. For all api calls you make, you should attach a token to them. The back-end should then, for every call a logged in user makes, do a few things:

  • Verify the token signature
  • Grab the user id from token and grab permissions/roles
  • Check if user has access to the api call or not

That way, even if by some miracle someone changes the token and passes the verification, which isn't likely, the entry in the database remains unchanged, and will not let them access the data they're looking for.

You could protect API routes by role, or even more granular, by a specific permission flag. Since you're doing the roles, most back-end frameworks let you apply some middleware, so apply a role-check middleware for all admin calls, another one for all logged in user calls, and that'll save you a bunch of trouble.

Remember, whenever you do client-side filtering, you're putting all your security on the hackers' laptop.

Edit: Addressing the comment

JWT just provides a standardized approach to the issue is all.

Let's for a second assume that your session variable stores the following info {'userid': 5, 'position': manager}. So if I'm a nice attacker, I'll just change manager to admin, see where the gets me, but if I'm not nice, I'll try all user ids from 0 to 10000, both with manager and admin as the position, see where that gets me. So then you try a random user id, like a hash of the timestamp. Still, attack surface has only increased somewhat. Now you're mad at these hackers. You're like okay, I'm going to encrypt this session variable, but I can't really store the key in React, since that's hackable, I'll just attach the encrypted thing to every request, decrypt it, verify it, and approve if it's valid. At which point, you basically almost re-invented JWT.

Predrag Beocanin
  • 1,402
  • 3
  • 18
  • 25
  • I was thinking of using JWTs for this but ran along a few people who advocated against it, one in a post being: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/ . I have a session variable that stores the user's information, for example `position:manager`. I suppose another way to word this entire question would be, how to securely obtain the session info and use it to display components without the client manipulating it. As soon as I retrieve session data and use it for test cases, it becomes a security issue as the user could change the input to change the display. – chickadee Jan 25 '19 at 21:34
  • For example, if someone logs in and has the server authenticate them and provide a session variable along with the response of the user info - I can store `user.position` in state, which could theoretically later be used to determine whether components are displayed or not based on that condition. Problem is user can manipulate that state and gain access to the components anyways if we use conditional display. – chickadee Jan 25 '19 at 21:44
  • I've somewhat addressed the comment by updating the answer. That's the thing - you can't store much in the state, except a token of some sorts.JWT contains user info, and is easily decryptable even without the key, but can't be verified unless the person trying to spoof has access to your secret. Some of the issues mentioned in the blog can easily be addressed. It's not a bulletproof system, but if the question is how do I know which user is what role, securely, that's the answer. – Predrag Beocanin Jan 25 '19 at 21:48
  • Okay let's assume there's a jwt with `user.position` inside - If you do something like decode(jwt) and get a hold of that, you would still store it in some variable or state before doing a conditional test on your components. At that point, it's `if { some_variable_that_the_client_can_change === true? show() : deny() }` – chickadee Jan 25 '19 at 22:00
  • But there isn't. There's a signed jwt with a userid inside, some other useless data like issuer and what not. You grab it on the back-end, check if you can verify it. If you can, nobody messed with it, and it's a valid token. Then you check what the user wants. If they want admin data, check the database if the user is an admin. If they are, return the data. If they aren't, return an error. React side, all you do is store the token you get from login, and attach it to every request – Predrag Beocanin Jan 25 '19 at 22:07
  • The tokens are usually signed with a secret, so unless the attacker has access to that information, which they shouldn't, as it's carefully stored in the backend, they can generate tokens, which wouldn't pass the first test - are they valid. You don't tell the api what permissions you have, only who you are and what you want, at which point the api decides should you see it or not. 'Who you are' should be transmitted in a secure, verifiable way – Predrag Beocanin Jan 25 '19 at 22:09
  • And then finally, the components are dumb components, meaning they just render the data provided. The only reason to ever store `user.position` inside a token is to render additional navigation items for admins, or control items like 'delete user'. Navigation items that lead to pages only, where pages request admin-level data from api, which api would decline, if I simply took my token and changed the `user.position`. Control actions would also be declined by the api, if I tried to delete a user eg while not being an admin, even though my token says `user.position = 'admin'` – Predrag Beocanin Jan 25 '19 at 22:10
  • Connecting to the server to request or manipulate data, this makes sense 100%. The server shouldn't let you do anything where the jwt isn't verified. But what happens if you have pure components you wish to display, not dealing with any server data. Let's say you have a dashboard with menu items where some should only be displayed if you're `admin`. In order to determine whether to display these, you do a conditional test. In that conditional test, you're using a variable that the user can change like `user.position` obtained before - without ever doing a GET or POST or whatnot. – chickadee Jan 25 '19 at 22:22
  • 1
  • Wait, but if I didn't want the user getting insight into what the managers or admins have access to (those menu items for example), is there a way around that then? It seems like the only way this is possible is if these menu items are requested from the backend server - which seems a bit of an overkill. – chickadee Jan 25 '19 at 23:04
  • @Schnappi yeah, that’s one way, and it really is an overkill. Another one would be making an entirely separate app for admins, on a different domain, so a malicious user can’t get past a login screen, even if they find it. – Predrag Beocanin Jan 25 '19 at 23:14