Learn how to implement Policy-Based Access Control (PBAC) in React applications using dynamic server-side permissions. This example explores fetching user roles and permissions after login, dynamically updating the client-side authorization state using CASL, and managing component-level access with reusable utilities and context. Through step-by-step guidance, you'll see how actions, resources, and roles are decoupled from static code, enabling real-time updates and flexible permission handling for buttons and components.
I have already talked about authorization models and authorization processes with React CASL. In this article, I will talk about how Permissions are obtained from the Server and applied dynamically to the Client.
In summary, I talked about authorization models in Authorization — I, which authorization model and which library we use in our applications.
At the Routing level, I explained the authorization by Roles, that is, the behaviors that the application should exhibit according to whether the person in this role is authorized to see this page or not.
In Authorization — II, we added mechanisms that allow some actions (Create, Delete, etc.) in the Task Management application to be shown and hidden according to roles. In summary, we went from the page level to the authorization of the elements on the page.
In today’s article, we will focus on how authorizations can be done dynamically using the authorizations in the database on the server side.
The working mechanism will be as follows
First of all, our application will start with a minimal Permission, for example, considering that anyone who can login to the Home page can log in, ClientSide default Permission minimal authorizations will be static.
After login, after the user obtains the JWTToken, we will pull the Role and Permission information of the user from the server and update this information via CASL so that Routing and Component Level authorization can be performed.
In the example I developed for this article
When we apply different authorization groups to the relevant screen, you can see that some of the buttons do not appear.
In this example, Actions, Resources, Roles and their Permissions that I explained in my Authorization — I and Authorization — II blog posts should not be in the JS code part, because they will be dynamic.
First of all, the data we obtain on the server side will be in the form of action, subject tuple array.
const AdminAuthItems = [
{ action: "delete", subject: "article" },
{ action: "create", subject: "article" },
{ action: "update", subject: "article" },
{ action: "read", subject: "article" },
];
const UserAuthItems = [
{ action: "create", subject: "article" },
{ action: "update", subject: "article" },
{ action: "read", subject: "article" },
];
const GuestAuthItems = [{ action: "read", subject: "article" }];
In order for these to be operated on the CASL side, we need Context and the Can component to check whether they will be authorized or not.
mport {createContextualCan} from '@casl/react';
import {createContext} from 'react';
// ** Create Context
export const AbilityContext = createContext();
// ** Init Can Context
export const Can = createContextualCan(AbilityContext.Consumer);
Another issue is the utility functions that allow us to export my JSONs and create and update Ability.
import { Ability, detectSubjectType } from "@casl/ability";
export function buildAbilityFor(authItems) {
const ability = new Ability(authItems, {
detectSubjectType: (subject) =>
subject && subject.subject ? subject.subject : detectSubjectType(subject),
});
return ability;
}
export function updateAbility(ability, authItems) {
ability.update(authItems);
return ability;
}
First of all, we create an empty authorization set with the Provider Pattern method and give it to the ArticleTestPart component.
function ArticleMiniApp() {
return (
<AbilityContext.Provider value={buildAbilityFor([])}>
<div>
<ArticleTestPart />
</div>
</AbilityContext.Provider>
);
}
This is the part in the component where we give the Action type containing AuthButton and Subject.
<>
<h1>Create Article</h1>
<AuthButton title="Create" authAction={"create"} authSubject={"article"} />
<h1>Delete Article</h1>
<AuthButton title="Delete" authAction={"delete"} authSubject={"article"} />
</>
So how AuthButton decides whether to render the screen due to authorization. AuthButton controls this itself via Ability with <Can…
import { Can } from "./authorization/can";
export const AuthButton = (props) => {
return (
<div>
<div>{props.authAction}</div>
<div>{props.authSubject}</div>
<Can do={props.authAction} on={props.authSubject}>
<Button>{props.title}</Button>
</Can>
</div>
);
};
In essence, this middleware allows you to design your own components and conduct CrossCut tasks (authorization, i18n, l10n, and theme) at these levels.
Now let’s dynamically apply different authorization groups. Here, through PermissionSelector, different authorizations are executed on the screen when you press the relevant buttons as if you get different authorizations from the server and update them.
import React, { useContext } from "react";
import { updateAbility } from "./authorization/ability";
import { AbilityContext } from "./authorization/can";
function PermissionSelector() {
const ability = useContext(AbilityContext);
const AdminAuthItems = [
{ action: "delete", subject: "article" },
{ action: "create", subject: "article" },
{ action: "update", subject: "article" },
{ action: "read", subject: "article" },
];
const UserAuthItems = [
{ action: "create", subject: "article" },
{ action: "update", subject: "article" },
{ action: "read", subject: "article" },
];
const GuestAuthItems = [{ action: "read", subject: "article" }];
return (
<>
<p>Please Click below link to See Authorization in different Roles</p>
<button onClick={() => updateAbility(ability, AdminAuthItems)}>
Admin Permission
</button>
<button onClick={() => updateAbility(ability, UserAuthItems)}>
User Permission
</button>
<button onClick={() => updateAbility(ability, GuestAuthItems)}>
Guest Permission
</button>
</>
);
}
export default PermissionSelector;
In the code above, the permission (AuthorizationItems) available on each button, the context is updated again with the updateAbility function.