https://loopback.io/doc/en/lb4/Authentication-overview.html
The steps which are mentioned below is to add the Authorization part in your project if Authentication is already implemented but If you haven't implement that then you can refer this repo How to Add Authentication in Loopback4.
NOTE: The steps and the project setup is taken into the continuation of the Previous project that is Authentication in Loopback4. So can go through the Readme and get to know about it. How to Add Authentication in Loopback4.
git clone https://github.com/HrithikMittal/loopback4-authorization
cd loopback4-authorization
npm install
npm run build
npm run migrate
npm start
-
Add the permission property into the User model
@property.array(String) permissions:String[];
-
Create a Admin controller which will be empty.
Now create a
Permission-keys.ts
where add permission according to your scenarioexport const enum PermissionKeys { // admin CreateJob = 'CreateJob', UpdateJob = 'UpdateJob', DeleteJob = 'DeleteJob', // normal authenticated user AccessAuthFeature = 'AccessAuthFeature', }
Now create a signup route for admin and add the permissions
async createAdmin(@requestBody() admin: User) {
validateCredentials(_.pick(admin, ['email', 'password']));
admin.permissions = [
PermissionKeys.CreateJob,
PermissionKeys.UpdateJob,
PermissionKeys.DeleteJob
];
admin.password = await this.hasher.hashPassword(admin.password);
const savedAdmin = await this.userRepository.create(admin);
delete savedAdmin.password;
return savedAdmin;
}
-
Create
Job
model, repository and controller(CRUD controller) to apply the role based access controller.@property({ type: 'number', id: true, generated: true, }) id?: number; @property({ type: 'string', required: true, }) title: string;
Now implement the Global interceptor
lb4 interceptor authorize
It will run before every controller.
Now add the authorize decorator with needed permission in the controller.
Go to the job Create controller
@authenticate('jwt', {required: [PermissionKeys.CreateJob]})
-
Now create a file
tyes.ts
import {PermissionKeys} from './authorization/permission-keys';
export interface RequiredPermissions {
required: PermissionKeys[];
}
Now go to the authenticate interceptor and add the Metadata
// first inject it
constructor(
@inject(AuthenticationBindings.METADATA)
public metadata: AuthenticationMetadata
) {}
.
.
.
console.log('Log from authorize global interceptor')
console.log(this.metadata);
// if you not provide options in your @authenticate decorator
if (!this.metadata) return await next();
const requriedPermissions = this.metadata.options as RequiredPermissions;
console.log(requriedPermissions);
.
.
.
(check it in the file)
-
Now again go to the
types.ts
and implement the interfaceMyUserProfile
export interface MyUserProfile { id: string; email?: string; name: string; permissions: PermissionKeys[]; }
-
Now to get the User Profile object we add dependency injection in
authorize interceptor
// dependency inject @inject.getter(AuthenticationBindings.CURRENT_USER) public getCurrentUser: Getter<MyUserProfile>,
-
Now what you have done in [Step7: How to Protect a route] go to the
verifytoken
method injwt service
and add the permissions in return objectasync verifyToken(token: string): Promise<UserProfile> { if (!token) { throw new HttpErrors.Unauthorized( `Error verifying token:'token' is null` ) }; let userProfile: UserProfile; try { const decryptedToken = await verifyAsync(token, this.jwtSecret); userProfile = Object.assign( {[securityId]: '', id: '', name: '', permissions: []}, { [securityId]: decryptedToken.id, id: decryptedToken.id, name: decryptedToken.name, permissions: decryptedToken.permissions } ); } catch (err) { throw new HttpErrors.Unauthorized(`Error verifying token:${err.message}`) } return userProfile; }
and also return permissions in the return
convertToUserProfile
method inuser service
[Step4:]return { [securityId]: user.id!.toString(), name: userName, id: user.id, email: user.email, permissions: user.permissions };
-
Now go back again to the
interceptor
and add the all the checks hereconsole.log('Log from authorize global interceptor'); console.log(this.metadata); // if you not provide options in your @authenticate decorator if (!this.metadata) return await next(); const requriedPermissions = this.metadata.options as RequiredPermissions; // console.log(requriedPermissions); const user = await this.getCurrentUser(); //console.log('User Permissions:', user.permissions); const results = intersection(user.permissions, requriedPermissions.required) .length; if (results !== requriedPermissions.required.length) { throw new HttpErrors.Forbidden('INVALID ACCESS'); } const result = await next(); // Add post-invocation logic here return result;
(you can check the file in the folder)
-
Now add the decorator to the controller on which you want to add the authorization like this
@authenticate('jwt', {required: [PermissionKeys.CreateJob]})
(Re-migrate your database like this)
npm run clean; npm run build; npm run migrate