What Is ExpressWebJs?
ExpressWebJs is a powerful and flexible Node FrameWork for building efficient and scalable backend services for modern applications. It is designed to be easy to use and flexible, allowing you to build a wide range of backend services.
One of the key features of ExpressWebJs is its robust routing system, which allows you to easily define and manage the routes of your application. It also includes built-in support for data validation, a database abstraction layer, queues and scheduled jobs, and tools for unit and integration testing.
In addition to these features, ExpressWebJs also offers thorough dependency injection, making it easy to manage and maintain the dependencies of your application. It is fully integrated with Typescript, allowing you to take advantage of the powerful features and excellent developer experience provided by both ExpressWebJs and Typescript.
Authority and Policies
ExpressWebJs provides a simple way to authorize user actions against a given resource. For example, even though a user is authenticated, they may not be authorized to update or delete certain records in the database. ExpressWebJs authorization features provide an easy, organized way of managing these types of authorization checks.
To make this happen, you need to import the Authority
module from Elucidate/AuthorizationFilter
. This helps you define your authorization logic as actions or policies in a well-organized manner.
Defining Authority
You need to import Authority
module from Elucidate/AuthorizationFilter
and define your authorities in the boot
method of any of your service providers, let's say in AppServiceProvider.
import { ServiceProvider } from "Elucidate/Support/ServiceProvider";
import { Authority } from "Elucidate/AuthorizationFilter";
export class AppServiceProvider extends ServiceProvider {
/**
* Register application services.
* @return void
*/
public register(): void {
//
}
/**
* Bootstrap any application services.
* @return void
*/
public async boot(): Promise<void> {
Authority.define("UpdateCompany", (user: IUserModel, company: ICompanyModel) => {
return company.user_id === user.id;
});
}
}
In this instance, we have established a procedure to assess whether a user possesses the authority to modify a specific App\Model\Company model. This authorization is achieved by comparing the user's id with the user_id associated with the individual who originally created the company.
Actions always receive a user instance as their first argument and may optionally receive additional arguments such as a suitable model.
You can even define multiple authorization actions by chaining the define method.
Authority.define("UpdateCompany", (user: IUserModel, company: ICompanyModel) => {
return company.user_id === user.id;
}).define("RegisterCompany", (user: IUserModel) => {
return user.isActive;
});
Once you have defined the action, you can access it anywhere in your application ( in the Route, Controller, Service, Repository) using the Authority Authorize method.
Authorizing Action
The Authority.authorize
method accepts the action name and the arguments it needs. The user is inferred from the currently authenticated user. Hence there is no need to pass the user explicitly.
Authority.authorize("UpdateCompany", companyRecord);
The authorize method throws an AuthorizationException if the user is not allowed to perform the given action.
Authorizing Other User
If you want to authorize other users that is not the currently authenticated user to perform an action, you may use the forUser
method of the Authority:
Authority.forUser(user).authorize("ViewReport", Report);
Creating Policy
Policies are classes for structuring authorization logic based on specific model or resource. Attempting to encapsulate all application permissions within a single file is not feasible. Therefore, Authority enables you to separate and manage permissions by moving them into dedicated policy files.
To create a policy, you can create a folder called Policies to house all your policies. It is totally up to you to name the folder anything. But the name should reflect what it is used for.
In this example, we are going to create a company policy inside a policies folder located in the APP/Policies directory.
Every policy class public methods are treated as the policy actions. This works similarly to the Authority actions. The first parameter is reserved for the user, and the action can accept any number of additional parameters.
// App/Policies/CompanyPolicy.ts;
import { ICompanyModel } from "App/Model/Types/ICompanyModel";
import { IUserModel } from "App/Model/Types/IUserModel";
export class CompanyPolicy {
public view(user: IUserModel, company: ICompanyModel): boolean {
return user.company_id === company.id;
}
public registerCompany(user: IUserModel): boolean {
return user.isActive;
}
public updateCompany(user: IUserModel, company: ICompanyModel): boolean {
return user.id === company.user_id;
}
}
Let's pick the update company method. This will receive a user and a company instance as its arguments and should return true or false indicating whether the user is authorized to update the given company record. So, in this example, we are verifying that the user's id matches the user_id on the company.
Feel free to create additional methods within the policy as required to handle the different actions it grants authorization for.
Authorizing Actions Using Policies
Once the policy is created, you can access it using the Authority.with
method. It's that easy.
Authority.with(CompanyPolicy).authorize("updateCompany", Company);
For example in CompanyController register company method.
public async register(req: Request, res: Response) {
const validation = await CompanyRegistrationValidation.validate<CompanyRegistrationDTO>(req.body);
if (!validation.success) return this.response.BAD_REQUEST(res, validation);
Authority.with(CompanyPolicy).authorize("registerCompany"); 👈 //checking registerCompany
//authority with CompanyPolicy
const response = await this.companyService.registerCompany(validation.data);
return this.response.setStatusCode(res, response.code, response);
}
Via Controller Helpers
Instead of importing the Authority module in the controller, ExpressWebJs provides a helpful authority method to any of your controllers which extends the App/Http/Controller/BaseController base class.
import { Request, Response } from "Config/Http";
import { BaseController } from "App/Http/Controller/BaseController";
import { CompanyService } from "App/Service/CompanyService";
import { CompanyRegistrationValidation } from "../Validation/CompanyRegistrationValidation";
import { CompanyRegistrationDTO } from "App/DTO/CompanyDTO";
import { CompanyPolicy } from "App/Policies/CompanyPolicy";
export class CompanyController extends BaseController {
constructor(private companyService: CompanyService) {
super();
}
public async register(req: Request, res: Response) {
const validation = await CompanyRegistrationValidation.validate<CompanyRegistrationDTO>(req.body);
if (!validation.success) return this.response.BAD_REQUEST(res, validation);
this.authority.with(CompanyPolicy).authorize("registerCompany"); 👈 //checking registerCompany
//authority with CompanyPolicy
const response = await this.companyService.registerCompany(validation.data);
return this.response.setStatusCode(res, response.code, response);
}
}
Conclusion
In conclusion, ExpressWebJs’s authorization system is a powerful, flexible, and efficient way to manage user access and permissions within your web application. By leveraging Authority for simple, global authorization checks and policies for model-centric and granular permission control, developers can create secure and compliant applications that provide a tailored user experience.
This practical guide has demonstrated the key concepts and usage of Authority and policies in ExpressWebJs, allowing you to confidently implement a robust authorization system in your projects.
Star to support ExpressWebJs on GitHub