REACT ARCHITECTURE SERIES
React Project Architecture 19 — (Authorization — 1)
I have talked about authorization models before. In this article, we will focus on how to perform authorization operations with React CASL.
I’ve discussed topics in web apps. Essentially, the principles are the same, but we implement them utilizing a variety of open source / third-party libraries and services, or by building them in-house. Even if our methods or libraries differ, the principles and aims remain the same.
Now let’s talk about authorizing our project with CASL.
An Example Use Case
The user has arrived at the URL where the web application is located.
- First of all, he/she needs to identify himself/herself, i.e. complete the Login process, after which the user receives a Bearer JWT Token to access the backend API.
- After that, the user should access their profile. The new user’s information, role, etc. etc. We have a user with a role. This role can be Admin, User, Student, etc.
- After that, we need to authorize according to the user’s role when accessing UI resources. These UI resources can be a Page, a component in the Page, or the authorization to press a button.
This is where Authorization Models come into play. If you do not have detailed information on this subject before, I recommend you to read my blog post below.
The most flexible of these models is Attribute-Based Access Models (Policy-Based) authorization. Different UI Frameworks can use CASL to create this type of authorization infrastructure.
So how do we perform this process technically? First we need to define our capabilities and permissions.
import { AUTHORIZATION_ACTIONS as ACTIONS } from "./authorization-actions";
import { AUTHORIZATION_RESOURCES as RESOURCES } from "./authorization-resources";
import { AUTHORIZATION_ROLES as ROLES } from "./authorization-roles";
Basically we have 3 models.
Actions: Which actions we can do. I only thought of accessing one page here, you can diversify it as you wish.
export const AUTHORIZATION_ACTIONS = {
ACCESS_PAGE: "access_page",
};
Resources: In this section, I only considered the pages inside after logging in as a resource, you can diversify this as the level and component you want.
export const AUTHORIZATION_RESOURCES = {
DASHBOARD: "dashboard",
PAYMENT: "payment",
};
Role: Which roles will have users on your system.
export const AUTHORIZATION_ROLES = {
ADMIN: "ADMIN",
USER: "USER",
};
In our scenario, let’s create an authorization structure, Admin can access all pages, User cannot access the Payment page.
import { Ability, AbilityBuilder } from "@casl/ability";
In this case, we will benefit from casl Ability and AbilityBuilder modules. We will offer a service through a structure where we determine the rules according to the roles as below.
export const AppAbility = Ability;
export default function defineRulesFor(role) {
const { can, cannot, rules } = new AbilityBuilder(AppAbility);
switch (role) {
case ROLES.ADMIN:
can(ACTIONS.ACCESS_PAGE, RESOURCES.DASHBOARD);
can(ACTIONS.ACCESS_PAGE, RESOURCES.PAYMENT);
break;
case ROLES.USER:
can(ACTIONS.ACCESS_PAGE, RESOURCES.DASHBOARD);
cannot(ACTIONS.ACCESS_PAGE, RESOURCES.PAYMENT);
break;
default:
break;
}
return rules;
}
export function buildAbilityFor(role) {
return new AppAbility(defineRulesFor(role), {
detectSubjectType: (object) => object.type,
});
}
Our goal is to create the ability structure with a role we have when the user logs into the system. When we come to the first page of our web application, we may have logged into that system in another tab or we may be entering that system for the first time.
const ability = buildAbilityFor(user.role);
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<AbilityContext.Provider value={ability}>
<QueryClientProvider client={queryClient}>
<>
<App />
</>
</QueryClientProvider>
</AbilityContext.Provider>
</BrowserRouter>
</React.StrictMode>
);
In our example, let’s have a user who came to the system for the first time. Since he is not logged in, he has no role and will not get any authorization. Here but an Ability Context is created and this Context is passed to all components of the App with Provider Pattern.
Note: For detailed information about Provider Pattern, read the blog post below.
Now let’s come to the user role with JSON Web Token with JWT after Login… In this case, we will need to update according to the new Ability rules in the Context.
import { AbilityContext } from "authorization/can";
import defineRulesFor from "authorization/permissions";
//TODO fetch User Profile
const ability = useContext(AbilityContext);
ability.update(defineRulesFor(user.role));
Now we can use it in different parts of our Ability application. For example, for Routing, which already has Private/Public pages for Routing, we can extend Routing a little more and say, does the user have authorization? Otherwise we can redirect to the not_authorized page.
export function RouteWithRedirections({ ...props }) {
const ability = useContext(AbilityContext);
const isAuthanticated = isUserLoggedIn();
//Authorization Over RouteId...
const routeId = getRouteId(props?.routeData?.path);
const isAccess = ability.can(AUTHORIZATION_ACTIONS.ACCESS_PAGE, routeId);
//Below Authantication Flow Executed...
if (isAuthanticated) {
if (isOutSidePage(props?.routeData?.path)) {
return <Navigate to={getRoutePath(ROUTES_ID.dashboard)} />;
} else {
if (isAccess) {
//Inside Pages also Check isAccess
return <>{props.children}</>;
} else {
return <Navigate to={getRoutePath(ROUTES_ID.not_authorized)} />;
}
}
} else {
if (isOutSidePage(props?.routeData?.path)) {
return <>{props.children}</>; //Not redirect user are outside and anonymous
} else {
return <Navigate to={getRoutePath(ROUTES_ID.login)} />;
}
}
}
Continue Reading 😃
You can click on this link for the continuation of this article or to access other articles in the article group (react-architecture).
You can click on this link for the continuation of this article or to access other articles in the article group (react-routing).