OutSystems React/Mobile — The 10 rules of role-based security

Filipe Morais
Noesis Low-Code Solutions
13 min readSep 15, 2020

Role-based security is essential in most applications. In this article, we will go in-depth on how OutSystems REACT handles role-based security, what we can and cannot do, and learn some patterns to protect our apps against malicious individuals.

I’m going to walk you through my experiments and attempts at bypassing built-in security and tampering data and I will leave you with a set of 10 rules, that if you follow, should keep you out of trouble.

For my experiments, I used and abused the developer console network sniffer of Firefox and I used a simple Firefox extension called “HTTP Request Maker” to execute post requests.

You can download here most of the code I did while writing this piece and if you have any questions or suggestions, you can reach me out at the Noesis virtual booth on NextStep.

Starting off simple

Let’s assume we have this simple screen that allows me to view and edit “Customers” with 3 roles:

Image 1: Simples screen with 3 roles. People with the view role can access the screen

Up until here, everything is standard and something you are probably very familiar with. I pick the view role for the screen, and:

1: If the user doesn’t have any of the roles picked on the screen, he won’t be able to access the screen. If the user has ANY of the roles picked on the screen, he will be able to access the screen.

Screen elements visibility

Image 2: Many people do roles on React as if they were working on classic web. Don’t do it, there is a better way!

Do not retrieve permissions as you did on the classic web! You don’t need, nor should, fetch them from a data action or a server action. Use the built-in client-side roles!

To show or hide something based on the role, we can use the javascript API, by calling the following function (hint: use ctrl-space to help you auto-complete):

$public.Security.checkIfCurrentUserHasRole($roles.<role>);

Image 3: How to correctly fetch roles on react

As seen on image 3, we can do this on the initialize event without affecting performance and attribute the boolean result to a screen variable, then we can use that screen variable to hide or show elements on the screen:

Image 4: Hiding a screen element client side

But take into consideration that this is client-side and can easily be tampered with by someone. I can still manually create a post request and bypass your if! So you need to secure your endpoints as we will see further below.

2: To hide or show elements on the screen based on roles, use the javascript API to fetch the roles, assign them to screen variables, and then use those variables to control the visibility of the screen elements. Just remember that this is just to show/hide something, it doesn’t ensure that someone doesn’t use a client action he shouldn’t be tampering with the system.

Data Fetches

React data fetches are asynchronous, so how does OutSystems handle security on those items? Are they safe?

Let’s open the network sniffer and take a look:

Image 5: Inspecting the post request made when the system loads the aggregate data

As we can see in image 5, OutSystems turns that “GetCustomers” screen aggregate into a post endpoint named “ScreenDataSetGetCustomers” that exists within the namespace of the “Customers” screen. But is this endpoint secure? Does the screen role protect the endpoint?
And the answer to both questions is yes!:

Image 6: Manually executing the POST request for Aggregate GetCustomers
Image 7: Manually executing the POST request for Aggregate GetCustomers, but from a user that doesn’t have the “Customer_view” role

To test this, I manually executed the post call for the GetCustomers aggregate but using the token from a new logged-in user that doesn’t have the “Customer_view” role. As expected, the request failed with a “Customer_View Role required” exception, and thus, the endpoint is secure.

Running the same experiment for Data Actions, I obtained the same results, and as such, we can conclude that:

3: As long as a screen is protected by roles, all aggregates and data actions within that screen are protected by the same set of roles.

Data Fetches: Advanced Scenarios — Lists

Now, let’s assume I have an ACL (access control list), that controls who can view a customer. Only users on the ACL will have access to that Customer:

Image 8: ACL data model where only users within the ACL can see customers

If we are making a list, we should filter this on the query itself:

Image 8: Query filtering by the ACL userId and payload being sent

And by looking at the payload, we can observe that this GetUserId() is never sent on the request, i.e., it runs server-side and thus no hacker can tamper with the action inputs.

And as such, we can conclude that:

4: It is safe to filter by GetUserId(), checkrole, or any server action directly on a screen aggregate to provide additional security, as the result will be fetched server-side and will not be sent on the request.

But if instead, I set the userId as a screen variable, the result raises an alarm:

Image 9: Query filtering by the ACL userId using a client-side variable and payload being sent

As we can see in image 9, the userId variable is sent on the request and a malicious individual could modify the variable and obtain information he should not be allowed to access. As such:

5: It’s perfectly fine to use screen variables for UI logic (eg: hiding/showing things). But screen variables DO NOT provide security nor should be used to enhance security: screen variables are sent on the requests for data actions and aggregates and can easily be tampered with.

And if you find yourself on a situation where you need much more than the userId and you cannot do it without screen variables, just refactor your aggregate into a data action and recalculate those variables on the data action: problem solved!

Data Fetches: Advanced Scenarios — Details

Image 10: Checking permissions on initialize as it was done on classic web

What you shouldn’t do is perform the validations on the initialize event as it will slow down the screen for everyone. Just like on the lists, you should make the validation on the query, as shown below, and if the query returns nothing, then the screen will be shown as empty.

Image 11: Checking permissions the react way. Since I’m inner joining the ACL on Customer and user, if the user doesn’t have the ACL, the query will return nothing!

And for more advanced scenarios where checking the user is not enough and you need additional queries/processing: just use a data action and do everything inside it.

And no, it won’t be a problem if a user sees an empty screen, because only hackers will access the detail of something they shouldn’t see: on a well-designed application, we can assume that links to details are protected by business rules and no well-intentioned user will access the detail of something he shouldn’t see by accident.

If you are really concerned about the hacker seeing an empty page, just redirect him somewhere else on an after fetch.

6: Do not use the initialize event to perform security checks that require server-side processing. Do the security checks on aggregates and data actions.

Calls to server actions from client actions

What about client actions? How does OutSystems handle its security? How safe are they and what should I have to care about?

To save a customer, I have a client action that calls a server action, as shown below:

Image 12: Client action that calls a server action to create or update a Customer

By looking at the sniffer, I can verify that just like aggregates and data actions, an endpoint is created for this action:

Image 13: Sniffer output for the call to the CreateOrUpdate action

Notice that, as with aggregates and data actions, this endpoint is created within the namespace of the screen. And as expected, the same behavior we verified on the aggregates occurs, i.e., if I don’t have the role on the screen, I can’t manually call the action:

Image 14: Manually executing a post request to the createOrUpdate action with an authorized user
Image 15: Manually executing a post request to the createOrUpdate action with a user that doesn’t have the screen role

Ok, up until here everything seems fine. But what if I put this server action inside a client action, like this:

Image 16: Server action wrapped inside a client action

And instead call it on the screen, like this:

Image 17: Calling a server action wrapped inside a client action from a screen action

Would this still be safe?

I went a little further and for this experiment, I called this action on two screens, one with only the view role set, and another with all roles:

Image 18: Two screens that call the same action, one with all roles and another just with the view role

As it turns out, it seems that on this scenario, instead of creating the action within the namespace of the screen:

/ReactRolesDemo/screenservices/ReactRolesDemo/MainFlow/Customers/ActionCustomer_CreateOrUpdate

It created only one endpoint for both screens:

/ReactRolesDemo/screenservices/ReactRolesDemo/ActionCustomer_CreateOrUpdate

I tried again to make post calls to this service using different users and something interesting happened:

  • The user with the view role was successfully able to call the service with any payload
  • The user without the view role was being refused when I used the payload data from the screen that required the view role
  • The user without the view role was being successful when I used the payload data from the screen that did not require the view role

As I dug a little further with the sniffer, I noticed that while the calls for both screens were to the same endpoint, the payloads had a very interesting difference:

Image 19: Payload of the request to the screen with just the view role
Image 20: Payload of the request to the screen without the view role

So, it seems the system is using that variable “viewName” to determine where the request comes from and which kind of rules it should enforce.

But can I tamper with this?

What if I remove this argument and try to call it with a different user?

Image 21: Request denied when I tried to remove the ViewName variable from the payload

Denied!

And what if I use a screen that has a role that I have?

Image 22: Request denied when I tried to tamper with the ViewName variable

Denied again!

So, on a first look and with my sparse knowledge on security, I think we are on the clear:

7: If a server action used on a screen relies on the same roles set on the screen, those actions are role protected by default and you don’t need to do anything else.

But this is only good enough for simple apps with simple authorization patterns. On most complex apps, to avoid developer mistakes, you should really follow the pattern I will dive into on the next section on all public server actions exposed by your CS layer.

Actions with special permissions

For this scenario, let’s say that the user can view the screen with the view role, but needs an edit role in order to edit.

Image 23: Hiding the edit inside an if protected by a role

We have already seen that this is not safe per se. I can just make a post request to the endpoint manually or modify the Permissions.CanEdit on the browser tools. So, to be safe against tampering on these situations we have to put the security server-side, as such:

Image 24: Protecting an action with a role

Two typical scenarios that require even more advanced checks are ACLs and profiles:

  • A user can only modify one own’s profile, not someone else’s profile, but a user can see anyone’s profile. In this very common situation, many developers leave the createOrUpdate unprotected, allowing any less ethical individual to tamper with any profile. To protect the createOrUpdate, just check if the GetUserId() and the user being modified is the same. Pretty simple!
Image 25: Protecting a profile record using the user ID to avoid allowing any user to modify other user’s profile
  • For ACLs the process is similar, but you validate the GetUserId() against the ACL on a query, as seen below:
Image 26: Protecting a server action with an ACL

8: If a server action used on a screen relies on different roles than those set on the screen, or if the server action relies on custom security patterns such as ACLs and profiles, the action must be protected with any required security check server-side.

Blocks

Blocks wrap up everything we have learned up until now. Let’s assume we have the following:

Image 27: Block that requires de CustomerACL role to be shown within a screen

I can easily tamper with the CanViewCustomerACL client-side variable and gain access to a block I shouldn’t. To protect this block, a classic web developer’s first instinct would be to do something like this:

Image 28: Checking permissions on initialize as it was done on classic web

But we already talked about it and this is a bad idea, so how can we make the block secure?

Well, we do exactly as with details: we make the validation when we do the query:

Image 29: Protecting a block on the aggregate (left) or with a data action (right).

In this scenario, I prefer the aggregate approach on the left if the block is a list and the data action approach on the right if it is a detail. The reason is that on the first method I’m “hiding” the checkCustomer inside the aggregate, and I don’t like to hide things, but doing a data action for a list would require me to either abandon all optimizations OutSystems already performs regarding pagination or reimplement them manually using SQL, which since I’m paid by the hour (and I’m lazy) I really don’t want to do. On the detail, there are really no relevant optimizations I lose by using a data action, so I make the validation explicit, as shown on the right.

But we are not in the clear yet.

Even if you redirect the malicious user, the hacker will still have access to any exposed server actions within the block. To protect those actions against tampering, you just need to follow the principles already described in the previous section and use checkroles and any required additional security pattern on them:

Image 30: Protecting a server action used within a block with a checkrole

And concluding:

9: Blocks do not inherit the authorization patterns you used to show/hide them. Protect all the queries and actions on your blocks with the specific roles and patterns that authorize the data within the block.

Offline logic

10: To use role-based logic on offline actions, use the javascript API. Just don’t forget that you need to re-validate those roles server-side when the data syncs. Also, don’t store on the local storage sensitive data nor anything the user shouldn’t be able to access.

Offline is not the scope of this article and there is already a lot of documentation and articles on the subject. Besides, the 2 above-mentioned rules are quite self-explanatory, so, as long as you follow them, you should be fine.

And wrapping it up

1: If the user doesn’t have any of the roles picked on the screen, he won’t be able to access the screen. If the user has ANY of the roles picked on the screen, he will be able to access the screen.

2: To hide or show elements on the screen based on roles, use the javascript API to fetch the roles, assign them to screen variables, and then use those variables to control the visibility of the screen elements. Just remember that this is just to show/hide something, it doesn’t ensure that someone doesn’t use a client action he shouldn’t be tampering with the system.

3: As long as a screen is protected by roles, all aggregates and data actions within that screen are protected by the same set of roles.

4: It is safe to filter by GetUserId() directly on a screen aggregate to provide additional security, as the userId will be fetched server-side and will not be sent on the request.

5: It’s perfectly fine to use screen variables for UI logic (eg: hiding/showing things). But screen variables DO NOT provide security nor should be used to enhance security: screen variables are sent on the requests for data actions and aggregates and can easily be tampered with.

6: Do not use the initialize event to perform security checks that require server-side processing. Do the security checks on aggregates and data actions.

7: If a server action used on a screen relies on the same roles set on the screen, those actions are role protected by default and you don’t need to do anything else.

8: If a server action used on a screen relies on different roles than those set on the screen, or if the server action relies on custom security patterns such as ACLs and profiles, the action must be protected with any required security check server-side.

9: Blocks do not inherit the authorization patterns you used to show/hide them. Protect all the queries and actions on your blocks with the specific roles and patterns that authorize the data within the block.

10: To use role-based logic on offline actions, use the javascript API. Just don’t forget that you need to re-validate those roles server-side when the data syncs. Also, don’t store sensitive data nor anything the user shouldn’t be able to see on the local storage.

If you missed it before, all code I made to support this, can be downloaded here. I hope I helped you better understand how role-based security works on react and what you can and cannot do. If you have any questions or if I missed something important, feel free to reach out to me.

--

--