How to Use CASL for Implementing Authorization in React
- Share:
If you've ever built a frontend application, you might be familiar with the concept of feature togglingāessentially, showing or hiding features based on the identity of the currently logged-in user. This approach is particularly useful in applications where users have varying levels of permissions, requiring us to manage both access and interaction within the app carefully.
In this guide, we'll explore how to integrate feature toggling into a React-based application, specifically focusing on event and meetup platforms as our example. For this purpose, we'll use a feature toggling library by Permit.io, named permit-fe-sdk
. This library is part of the Permit SaaS ecosystem, offering a no-code solution for managing authorization and leveraging CASL (an isomorphic authorization JavaScript library) to handle complex permission logic.
Our project will simulate an event platform, somewhat akin to Eventbrite, where users can view, subscribe to, and manage their attendance at various events or meetups. Based on a user's subscription history and preferences, the platform will suggest events that might interest them.
š” For the sake of simplicity, this tutorial will use predefined, static users to demonstrate the core functionality, sidestepping the implementation of a full authentication system. In a real-world scenario, you would want to integrate a proper user login mechanism using your preferred authentication service.
Letās get to it -
Prerequisites
1. Setting Up Node.js and npm
Ensure you have Node.js and npm (Node Package Manager) installed on your machine. These tools are essential for managing the packages your project will depend on.
- Download Node.js: Go to the official Node.js website and download the installer for your operating system. npm is included with Node.js.
- Verify Installation: Open your terminal or command prompt and run the following commands to check if Node.js and npm are installed correctly.
2. Creating a Project Directory
Choose a directory where you want to create your project and run:
mkdir meetups-and-events
cd meetups-and-events
3. Initialize the Project
Initialize a new npm project by running:
npm init -y
This command creates a package.json
file in your project directory, which will track your project's dependencies.
- Install all required dependencies
npm install react react-dom react-router-dom
npm install --save-dev webpack webpack-cli
We need to install two sets of dependencies. One set is to allow permissions control via Permit, and itās frontend SDK.
npm install @casl/ability @casl/react permit-fe-sdk permitio
The other set is to allow us to create a nicer UI.
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
After installing the necessary packages, the next step is to configure them within your React application. This involves setting up Permit.io to manage feature toggling and using CASL to handle fine-grained access control within your app.
You'll typically start by initializing the Permit SDK with your project's configuration details and then defining your authorization rules with CASL. These rules will determine what actions users can take within your app, such as subscribing to events, accessing exclusive content, or managing their subscriptions.
Modeling the application
Step 1: User and Event Data Structure
We begin by establishing a basic user system and event data. In lieu of a login system, we'll use a mock approach to simulate user sessions.
// src/userManagement.js
export const users = [
{
id: 'user1',
role: 'attendee',
permissions: [
{ action: 'view', resource: 'event_basic' },
],
},
{
id: 'user2',
role: 'organizer',
permissions: [
{ action: 'view', resource: 'event_basic' },
{ action: 'view', resource: 'event_premium' },
],
},
];
export const getUserById = (userId) => users.find(user => user.id === userId);
Step 2: Context for User Session and Permissions
Next, create a context to manage user sessions and permissions throughout the app. Add a new file src/SessionContext.js:
import React, { createContext, useState, useEffect } from 'react';
import { Ability } from '@casl/ability';
import { getUserById } from './userManagement';
export const SessionContext = createContext();
export const SessionProvider = ({ userId, children }) => {
const [ability, setAbility] = useState(new Ability());
useEffect(() => {
const user = getUserById(userId);
const rules = user.permissions.map(({ action, resource }) => ({
action,
subject: resource,
}));
setAbility(new Ability(rules));
}, [userId]);
return (
<SessionContext.Provider value={ability}>
{children}
</SessionContext.Provider>
);
};
Step 3: Integrating the SessionProvider
Integrate SessionProvider
in your app's main component to manage user sessions. For demonstration, we'll hardcode a user ID.
In src/App.js
, wrap your components with SessionProvider
:
import React from 'react';
import { SessionProvider } from './SessionContext';
function App() {
const userId = 'user1'; // Example user ID
return (
<SessionProvider userId={userId}>
<div className="App">
{/* Your app's components */}
</div>
</SessionProvider>
);
}
export default App;
Step 4: Displaying Events Based on Permissions in React
This step involves fetching user permissions from your backend and conditionally rendering events based on these permissions. We'll use the React context set up in previous steps to manage user permissions and React hooks to fetch data from the backend.
import React, { useState, useEffect, useContext } from 'react';
import { SessionContext } from './SessionContext';
const EventsList = () => {
const ability = useContext(SessionContext);
const [events, setEvents] = useState([]);
useEffect(() => {
// Assume you have a predefined list of events
const allEvents = [
{ id: 1, title: 'Event Basic', type: 'event_basic' },
{ id: 2, title: 'Event Premium', type: 'event_premium' },
];
// Fetch permissions from the backend based on the logged-in user's ID
// For this example, the user ID is hard-coded
fetch('<http://localhost:3001/check-permissions>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: 'user1', // This should come from your auth system
resourcesAndActions: allEvents.map(event => ({ resource: event.type, action: 'view' })),
}),
})
.then(res => res.json())
.then(({ permittedList }) => {
// Filter events based on permissions
const permittedEvents = allEvents.filter((_, index) => permittedList[index]);
setEvents(permittedEvents);
})
.catch(error => console.error('Error fetching permissions:', error));
}, []); // Dependency array left empty to run once on component mount
return (
<div>
<h2>Available Events</h2>
<ul>
{events.map(event => (
<li key={event.id}>{event.title}</li>
))}
</ul>
</div>
);
};
export default EventsList;
Once we have our code configured, the actual permissions will not work because we still need to configure our authorization policies inside Permit.io. Letās do that -
Step 5: Creating a basic policy in Permitās no-code UI
To get started, make an account with Permit here. Permit is a full-stack authorization service that allows us to manage the policies we assign to each role with a no-code UI.
As you log in, you will get asked to create an organization and then you will be in the App!
The first thing you will need to do is retrieve the Permit API Key and pass it into the Permit instance object. Make sure to add this to your .env.local
file.
You can find your API key here:
You can also follow this simple Quickstart guide if you prefer.
Once thatās set up, we can start to configure our policies to enforce access and make sure that only the users we assigned specific roles get shown specific components in the UI.
Let's get started -
Configuring the policy in Permit.io
1. Setting up a basic policy in Permitās no-code dashboard
Navigate to the Policy page - you can find it on the left navigation bar.
As we have no roles yet, as soon as we click āManage Rolesā we will be prompted to create our first role. Letās do that!
We only need two roles; letās call them Basic Meetup Viewer
and Premium Meetup Viewer
roles. We can add a short description to each of the roles if we want to. For this example, the naming is self-explanatory, so a description is unnecessary.
We created two roles! Now we need to add a resource, something we will be performing an action onāāāand thus testing if we have the permission to do so.
Navigate to the Resources tab at the top, and letās create four new resources. In this case, each component of the page that we conditionally want to render per each user should be its own resource.
Letās create:
- Meetup A
- Meetup B
- Meetup C
As we create our resource, we need to specify the actions on that resource. As we are working with just viewing components, we should have a specific action for that component, and we can call it view
.
š” We can have other actions for each component if your functionality is more complicated or you want a user to interact with that component in a restricted way. However, for this demo example, we will just work with one action.
And here is all our resource created:
Great! We are all good to go to enforce the policy in our code.
2. Working with the Policy Editor
Now that we have our policy ready, letās check some actions, letās change between users, and see how the UI changes!
3. Implementing guards in our code to only render specific UI components
For the meetups, we are now conditionally rendering them based on the permitState
that comes back from our policy engine. Below are the two implementations of the guards:
{permitState?.check("view", "basic_meetup")
ā ? products.map((product) => (
ā // Rest of code
))
: null}
{permitState?.check("view", "premium_meetup") ? (
// Rest of voucher code
) : null}
The last thing that needs to be done now is to run the Permit policy engine and connect your application.
Connecting your application
- Pull the docker image
docker pull permitio/pdp-v2:latest
2. Run the docker image with the Environment API key. In this case, this is the API key we are storing in our .local.env
file.
docker run -it -p 7766:7000 --env PDP_DEBUG=True --env PDP_API_KEY=<YOUR_API_KEY> permitio/pdp-v2:latest
And hurray! You have a working application!
Conclusion
Navigating the complexities of permission management and the use of feature toggles can seem daunting at first. However, the adoption of specific tools and frameworks, including React, can simplify these tasks considerably. This strategy is crucial for managing sections of the user interface that need tight restrictions to block unauthorized use.
By leveraging solutions like Permit.io and CASL, developers can find a clear and efficient path to handle permissions, blending ease of integration with high-level security measures. These tools are crafted to simplify the permissions management landscape, making it more approachable for developers tasked with implementing precise access controls while ensuring robust security.
For anyone looking for further insights or assistance with Permit.io and CASL, our Slack community provides a welcoming space for inquiries and knowledge exchange. Moreover, our extensive documentation offers an in-depth look at intricate permission management, serving as a valuable tool for those aiming to master and apply these access controls with effectiveness.
Written by
Filip Grebowski
Developer Advocate at Permit.io, Software Engineer, and YouTube Creator