# Authorization
# Introduction
In addition to enabling you build a secured application, ExpressWebJs also 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 provider, 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; }); } }
Copied!
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; });
Copied!
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);
Copied!
The authorize method throw an AuthorizationException if the user is not allowed to perform the given action.
# Authorizing Other User
If you want to authorize other user 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);
Copied!
This also works with policy. Let's say you have a ReportPolicy class where you define all your authority actions, you can call the with
method after the forUser
method and pass in your policy before chaining the authorize
method.
Authority.forUser(user).with(ReportPolicy).authorize("ViewReport", Report);
Copied!
# 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 polices. 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 similar 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; } }
Copied!
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);
Copied!
let's say 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); }
Copied!
# Via Controller Helpers
Instead of import the Authority module in the controller, ExpressWebJs provides a helpful authority method to any of your controllers which extend 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); } }
Copied!