One of the major pitfalls in software development with React and the development process, in general, is security. As a matter of fact, security is just as important on the front end as it is on the server-side. Comparatively, security vulnerabilities on the front end can range from data leaks as a result of a poor state management approach to authentication and authorization. Broken access control is one of the security vulnerabilities that exist with respect to authentication and authorization.
What are some examples of React broken access control, and how can you as a software engineer lookout for these scenarios and prevent attackers from gaining unauthorized access to your application? In this post, I'll dissect and review these points. I'll also show you actionable steps to fix major vulnerabilities.
Broken Access Control in React
If a regular user gets access to the admin page, you know what that means. How do you avoid that? The act of avoiding that is broken access control.
In a nutshell, broken access control is enforcing a limit to what action a delegated user can perform.
Can an admin user edit a document? Yes. Can a regular user edit the document? No.
How do you enforce that rule and prevent things from going the other way around? In other words, how do you implement access control in this regard?
Your expectations might be high now, but before going all out, you need to ask: what happens when you don’t enforce access control? If access control is not enforced, an attacker can gain unauthorized access to sensitive data like cookie sessions that can break your application. Thus, the integrity of the application's logic is threatened.
Now that you know what broken access control is and why it matters, let's take a look at some examples of React broken access control.
Examples of Broken Access Control in React
What are examples of broken access control in React and recommended fixes? We'll take a look at:
unhandled redirects,
insecure direct object reference (IDOR), and
inadequate role-based authorization.
Unhandled Redirects
Redirects can seem minor when enabled on certain pages in a web application, but as an engineer, you should understand that redirect exists for the greater good. Likewise, granting that the validity of the accessTokens used during requests to the server is set for a particular period, what happens when the token is invalid? Do you have robust error handling in place to tell the user what to do and return the appropriate response? Or is there a security leak somewhere?
In all honesty, apart from improving the user experience of your web application as a noble act, redirects reduce or eliminate the likelihood of having an unauthenticated request into the system.
Fun fact: Hackers are experts that outsmart the engineer.
Insecure Direct Object Reference (IDOR)
IDOR is an access control vulnerability that occurs when an application fetches resources from an internal database based on a specified identifier without authentication or access control. A real-life scenario is with queries that are taken from URL parameters to fetch data from the server. For example, let's look at a user_id query on a URL:
https://www.xyz.com/user?user_id=566
The user_id parameter in the URL can be changed and, without authentication, send data about the specified user_id. That's IDOR in practice.
Inadequate Role-Based Authorization
How many types of users can access your system? For instance, let's consider a freelance platform. In a freelance platform, there are two types of users: buyers and sellers. How do delegated users access the application? Should a buyer have the same access as a seller? Are there other types of users apart from these two?
The assigned routes must handle the roles available in the application. However, if some routes aren't available or aren't supported by a user, there should be a base condition set in place to handle this to avoid malware insertion by any means.
How do you handle situations where access control might have been breached?
How to Fix and Prevent Broken Access Control in React
There are best practices for both fixing and preventing unhandled redirects, IDOR, and inadequate role-based authorization. To illustrate, let's see some measures you can implement to secure your React application against broken access control.
How Do You Handle Redirects in React? (Prevention)
The React library react-router-dom provides a redirect function. For those of you coming from a non-React background, react-router-dom is a React library that handles page routing in the DOM efficiently. Other frameworks like Vue also have their routing methods.
If you haven’t used react-router-dom before for handling authentication, here’s a simple implementation:
Import { Redirect } from ‘react-router-dom’;
Import { useAuth } from ‘../hooks’;
Const AuthGuard = () => {
Const { isAuth } = useAuth();
if(isAuth) {
Return <Redirect to=’/login’ />
}
}
The code snippet above implements the isAuth() function using the useAuth() hook to check if the user is authenticated. (The useAuth() hook is basically to check for the user authentication credentials and verify their validity under the hood.) However, if the user is not authenticated, the Redirect function is called to redirect the user to the login page to gain access.
A real-life example of this is if you try to perform an action on Twitter without being logged in. Twitter redirects you to the login page to see if you have the right permissions before granting you access.
How to Prevent IDOR in React
Next, let's talk about IDOR. How can you prevent IDOR vulnerabilities in your application?
Here are some tips:
Avoid displaying key details like IDs, filenames, and the like in the URL as much as possible.
Request authorization upon every request. This can be done by always checking if the request being made is authenticated. In short, you can implement this by adding an auth middleware to all required routes. That way, before serving the resources on an endpoint, the middleware verifies that the user’s credentials are valid. An example of the implementation but in another context is with redirects, as I explained earlier.
To sum it up (for IDOR), hide as many process implementations from the user as possible. Let users be users and feel like it.
Apologies if the last sentence was a bit stern. Security issues are to be treated with the gravity of the consequences in mind. On to the next!
How to Handle Inadequate Role-Based Authorization
The last React broken access control vulnerability we'll discuss is inadequate role-based authorization. To implement role-based authorization, we'll use a navigation tab for a login page:
const tabs = [
{ label: 'Patient', value: 'patient' },
{ label: 'Staff', value: 'staff' },
];
Next, a login function that gets the current tab authenticates the user based on the value:
const Login = () => {
const dispatch = useDispatch();
const [currentTab, setCurrentTab] = useState('patient');
const handleTabsChange = (event, value) => {
setCurrentTab(value);
};
const [inputs, setInputs] = useState({
email: '',
password: '',
submit: null,
});
return (
<div>
<h1 className='my-3'>Login</h1>
<Paper className='my-3'>
<Tabs
onChange={handleTabsChange}
scrollButtons='auto'
indicatorColor='primary'
textColor='primary'
value={currentTab}
variant='scrollable'
>
{tabs.map((tab) => (
<Tab key={tab.value} value={tab.value} label={tab.label} />
))}
</Tabs>
</Paper>
<Formik
initialValues={inputs}
validationSchema={Yup.object().shape({
email: Yup.string()
.email('Must be a valid email')
.max(255)
.required('Email is required'),
password: Yup.string().required('Password is required'),
})}
onSubmit={(values, { ...formiks }) => {
const { submit, ...rest } = values;
if (currentTab === 'patient') {
submitLogin(rest, patientLogin, { ...formiks, dispatch });
}
if (currentTab === 'staff') {
submitLogin(rest, staffLogin, { ...formiks, dispatch });
}
}}
>
I used the Formik library for form handling in place of using individual states. With respect to the dependencies building up, this might not be the best approach if you're working on a simple app. However, Formik allows us to use Yup for input validation. In other words, we're writing less code while also enforcing input validation on the front end.
The submitLogin() function handles the second aspect of the user authorization into the system. Firstly, a condition is set in place to check the selected login tab, after which the submitLogin() function takes in the required parameters, including the input values that Formik handled and validated.
However, the point of focus here is the patient/staff login, not the libraries used. In other words, the login workflow is our major concern. So, what happens in the login workflow?
The code checks the user logging in and verifies that the credentials are correct.
Then, the submitLogin() function authenticates the user.
The code example above is one way you can implement role-based access control in React. Other ways include React Context API and Redux.
Conclusion
React broken access control requires good inspection. Sometimes it might require professional software testers to work around your application to make sure it's bulletproof. In this post, I've covered three ways broken access control can manifest. I've reviewed those three instances and explained how to prevent them. We also went over how to handle the vulnerabilities both in theory and in practice.
If you take anything from this post, let it be this:
Nothing, absolutely nothing! Take the ability to probe everything and apply the security concepts you learn everywhere in software development.
That was a little out of context, yeah? Now you can have this to probe:
Always the roles when authenticating routes.
Avoid creating too many roles (simplify them as much as possible).
If you implement role-based access control in your application, ensure that it exists across all routes.
I hope you learned something new about React broken access control. See you around!
This post was written by Teniola Fatunmbi. Teniola is a computer science undergraduate and backend developer. He’s a JavaScript lover, a python nerd, and he likes to explore how data is used in software engineering.