This content originally appeared on Bits and Pieces - Medium and was authored by Lakindu Hewawasam
How To Design and Implement Secure Components with React
Almost all applications require users to authenticate before allowing access to the application and its features.
For example, imagine a scenario where you’re tasked to build an application for a nuclear reactor. Its’ main requirement is to allow authenticated and authorized users to click a button that would detonate a nuclear bomb somewhere in the world. If you implement the system allowing any user to see the detonate button, it will cause a significant global security breach and chaos all over!
Therefore, this article will explore how developers can incorporate access control into their React applications to build more secure components and applications efficiently.
The Basics — Backend Authorization
Every developer must ensure that all requests are authorized in the backend.
But why should I use backend authorization?
Developers can indeed implement client-side authorization and enforce security. Still, it’s essential to acknowledge that the front-end code can always be modified using the browser, allowing hackers to bypass your front-end authorization code and exploit the backend services.
Developers can enforce backend authorization in many ways. For example, for React applications, developers can create a JWT token upon successful authentication and pass in specific claims you can later use to authorize the user.
exports.handler = async (event) => {
// obtain jwt token
const token = event.headers.Authorization.split(' ')[1];
// verify jwt token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// obtain custom "permission" claims
const { permissions } = decoded;
// check if user has permission to access this function
if (!permissions.includes('admin')) {
return {
statusCode: 401,
body: JSON.stringify({
error: 'Unauthorized'
})
};
}
const queryResp = await dynamodb.query({
TableName: 'users',
KeyConditionExpression: '#email = :email',
ExpressionAttributeNames: {
'#email': 'email'
},
ExpressionAttributeValues: {
':email': event.queryStringParameters.email
}
}).promise();
return {
statusCode: 200,
body: JSON.stringify(queryResp.Items[0])
}
};
For example, consider the code snippet shown above. It illustrates a simple AWS Lambda function that allows the user table to get queried if the user has the “admin” permission. This is one of many ways in which you could implement access control.
For instance, you could set up request interceptors with middleware to authorize incoming requests. Or else, you could set up a utility method that authorizes the incoming request before serving a response. All in all, it’s mandatory to have backend authorization as the authorization logic since the front end (JavaScript code) can always be edited within the browser and bypassed.
Defining Secure React Components
After enforcing backend authorization, developers can start designing and implementing secure React components. There are many ways to go about this. Therefore, let’s look at each method one by one.
Pre-requisites
Before we implement secure React components, returning a list of permissions from the backend onto the client side is essential. Developers can fetch these permissions and store them in LocalStorage when the user logs in successfully. Later, these permissions can be used to implement granular access control for components.
const doLogin = () => {
const resp = await axios.post('/login', {
username: 'username',
password: 'password'
});
localStorage.setItem('token', resp.data.token);
localStorage.setItem('permissions', JSON.stringify(resp.data.permissions));
}
The snippet above illustrates a sample authentication request that your React application may make to obtain the JWT access token and the permissions. As depicted, the permissions are persisted in the LocalStorage for later use.
Method 01 — Conditional Rendering
First, there can be cases where developers may want to display only a certain component on one condition. In such cases, the conditional rendering approach can be adopted.
For example, if the user is logged in, you may want to show the Reactor component, but if not, you may want to display the Login component. How to use conditional rendering for such cases is shown below.
const App = () => {
const isLoggedIn = localStorage.getItem('token');
return (
<Fragment>
{isLoggedIn ? <Reactor /> : <Login />}
</Fragment>
);
}
The snippet above shows the Reactor component rendered if the user is logged in (only if the token is stored in LocalStorage).
Method 02 — Auth Guards
Sometimes, conditionally rendering components may not be enough. In such situations, you may want to prevent users from accessing specific routes in your application.
For example, in our nuclear reactor application, you may not want citizens to access the /detonate and /add-bomb routes. In cases where you wish to control access to specific routes of your application (rather than components within a route), you may consider using an Auth Guard.
You can implement an Auth Guard in React using the concept of component specialization. This is a React concept in which you use a base component to develop a specialized component. Thus, you can use the React Router to create the base Route component and a specialized GuardedRoute component that builds on the Route component.
Consider the snippet shown below.
import React from "react";
import { Route, Redirect } from "react-router-dom";
const GuardedRoute = ({ component: Component, auth, ...rest }) => {
<Route
{...rest}
render={(props) =>
auth === true ? <Component {...props} /> : <Redirect to="/forbidden`" />
}
/>;
};
export const Nav = () => {
return <GuardedRoute
auth={true}component={() => <h1>Have Access To Nuclear Bomb Management</h1>}
path={'/bomb-management'}
/>;
};
The snippet above creates a specialized component of the Route called the GuardedRoute.It is responsible for accepting an authentication parameter and rendering the component on the Route only if the authentication validation passes.
In our case, we have utilized the guard to give access to the bomb management console in the nuclear reactor.
Method 03 — Wrapper Components
Finally, there can be cases where you need to hide or disable specific content on a page from a category of users. In such cases, conditional rendering will get the job done. However, it makes your code complex and less readable, which makes it harder to maintain.
By way of illustration, consider a situation where a reactor manager and the president both access the /reactor-management route in our nuclear reactor.
The president must be able to view the Detonation section along with the users authorized to manage the reactor. On the other hand, the reactor manager should not be able to view these components.
In such situations, using conditional rendering would result in code duplication. Therefore, you can create a component that uses Reacts’ Containment principle where you make the parent component by passing children into it. This allows you to add authorization logic into the parent component and only render the children component if the user is authorized.
const Detenator = () => {
return (
<><button>Detonate Reactor</button></>
);
};
const verifyPermissions = (permissions, userPermissions) => {
let isVerified = true;
if (!permissions || !userPermissions) isVerified = false;
isVerified = permissions.every((permission) =>
userPermissions.includes(permission)
);
return isVerified;
};
export const Authorizer = ({ permissions, requiredPermissions, children }) => {
const isAuthorized = verifyPermissions(permissions, requiredPermissions);
if (isAuthorized) {
return children;
}
return (
<><h1>Forbidden. No permissions to access</h1></>
);
};
export const Page = ({ children }) => {
return (
<><h1>Nuclear Bomb Management</h1>
{children}
<Authorizer permissions={[]} requiredPermissions={["detonate", "president"]}>
<Detenator /></Authorizer></>
);
};
The snippet shown above creates a wrapper component — Authorizer that accepts children to render only if the user has the required permissions to access the children component.
It matches our use-case as the Detenator can only be accessed if the user has detonated and president permissions.
Concluding Thoughts
Security is always important in any application. Security must always be a key priority, even if you’re building an application as simple as a to-do app or if it’s as complex as a nuclear detonator.
This article has explored three ways developers can design and implement secure components in React to help improve their component security and application security.
The code implemented in this article is accessible in my GitHub repository.
I do hope that you have found this article helpful. Thank you for reading.
Go composable: Build apps faster like Lego
Bit is an open-source tool for building apps in a modular and collaborative way. Go composable to ship faster, more consistently, and easily scale.
Build apps, pages, user-experiences and UIs as standalone components. Use them to compose new apps and experiences faster. Bring any framework and tool into your workflow. Share, reuse, and collaborate to build together.
Help your team with:
Learn more
- How We Build Micro Frontends
- How we Build a Component Design System
- The Bit Blog
- 5 Ways to Build a React Monorepo
- How to Create a Composable React App with Bit
Designing & Implementing Access Control in React was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Lakindu Hewawasam
Lakindu Hewawasam | Sciencx (2022-09-28T07:02:38+00:00) Designing & Implementing Access Control in React. Retrieved from https://www.scien.cx/2022/09/28/designing-implementing-access-control-in-react/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.