I have implemented jwt authentication using custom user service which implements user-service from loopback authentication/jwt. Successfully issuing a token to the user.
But while implementing authorization, AuthorizationContextdoesn't contain role.
I have checked that AuthorizationContext .principals[0] contains only id and name, not the role.
AuthorizationContext.roles is also empty. Which clearly means authentication is not passing user property role to authorization
Here is the code
GIT repository: https://github.com/pratikjaiswal15/loopbackauthrization
user.model.ts
@property({
type: 'string',
})
role: string;
authorize.ts
import {AuthorizationContext, AuthorizationDecision, AuthorizationMetadata} from '@loopback/authorization';
import {securityId, UserProfile} from '@loopback/security';
import _ from 'lodash';
// Instance level authorizer
// Can be also registered as an authorizer, depends on users' need.
export async function basicAuthorization(
authorizationCtx: AuthorizationContext,
metadata: AuthorizationMetadata,
): Promise<AuthorizationDecision> {
console.log(authorizationCtx.principals[0])
console.log(authorizationCtx.roles)
// No access if authorization details are missing
let currentUser: UserProfile;
if (authorizationCtx.principals.length > 0) {
const user = _.pick(authorizationCtx.principals[0], [
'id',
'name',
'role', // propety role
]);
currentUser = {[securityId]: user.id, name: user.name, roles: user.role};
} else {
return AuthorizationDecision.DENY;
}
if (!currentUser.roles) {
return AuthorizationDecision.DENY;
}
// Authorize everything that does not have a allowedRoles property
if (!metadata.allowedRoles) {
return AuthorizationDecision.ALLOW;
}
let roleIsAllowed = false;
for (const role of currentUser.roles) {
if (metadata.allowedRoles!.includes(role)) {
roleIsAllowed = true;
break;
}
}
if (!roleIsAllowed) {
return AuthorizationDecision.DENY;
}
// Admin and support accounts bypass id verification
if (
currentUser.roles.includes('admin') ||
currentUser.roles.includes('support')
) {
return AuthorizationDecision.ALLOW;
}
/**
* Allow access only to model owners, using route as source of truth
*
* eg. @post('/users/{userId}/orders', ...) returns `userId` as args[0]
*/
if (currentUser[securityId] === authorizationCtx.invocationContext.args[0]) {
return AuthorizationDecision.ALLOW;
}
return AuthorizationDecision.DENY;
}
user.service.ts (authentication)
import {UserService} from '@loopback/authentication';
import {repository} from '@loopback/repository';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {compare} from 'bcryptjs';
// User --> MyUser
import {User} from '../models/user.model';
// UserRepository --> MyUserRepository
import {Credentials, UserRepository} from '../repositories/user.repository';
export class CustomUserService implements UserService<User, Credentials> {
constructor(
// UserRepository --> MyUserRepository
@repository(UserRepository) public userRepository: UserRepository,
) {}
// User --> MyUser
async verifyCredentials(credentials: Credentials): Promise<User> {
const invalidCredentialsError = 'Invalid email or password.';
const foundUser = await this.userRepository.findOne({
where: {email: credentials.email},
});
if (!foundUser) {
throw new HttpErrors.Unauthorized(invalidCredentialsError);
}
const credentialsFound = await this.userRepository.findCredentials(
foundUser.id,
);
if (!credentialsFound) {
throw new HttpErrors.Unauthorized(invalidCredentialsError);
}
const passwordMatched = await compare(
credentials.password,
credentialsFound.password,
);
if (!passwordMatched) {
throw new HttpErrors.Unauthorized(invalidCredentialsError);
}
return foundUser;
}
// User --> MyUser
convertToUserProfile(user: User): UserProfile {
let address = ''
if (user.address) {
address = user.address
}
const profile = {
[securityId]: user.id!.toString(),
name: user.name,
id: user.id,
email: user.email,
role: user.role,
address: user.address
}
console.log(profile)
return profile
}
}
@HrithikMittal: I think you could help @pratikjaiswal15 on the issue.
Yes sure @madaky and thank you for considering me into this
and @pratikjaiswal15 you have to bind your custom user service in your application.ts
if you need more help of how to do it please check this repo
https://github.com/HrithikMittal/Loopback4-auth
and moreover, I run your repo also and you got the error

This is because you have not created a password field which is needed in interceptor and also the keys.ts needed some bugs to fix as it should be in order
BindingKey.create<UserService<Credentials, User>
It is advisable to create a project again and before start writing code decide your use case and database modelling is must because when I check there is DB error also.
You can refer and complete docs from this:
Authentication: https://github.com/HrithikMittal/Loopback4-auth
Authorization: https://github.com/HrithikMittal/Loopback4-authorization
I hope this will help you out.
and I think @madaky you close this issue because there is no error related to the loopback4 authentication or authorization this is all because of wrong/incorrect setup of the project.
And thanks again @madaky
Okay thanks I will check it. Thank again
If it will solve your problem then close this issue.
I have already bound custom-user service in application.ts . See here (https://github.com/pratikjaiswal15/loopbackauthrization/blob/master/src/application.ts#L77)
You seem to have implemented authorization differently. I am looking for the standard way with @authorize decorator.
Can you help with that approach? Thanks
Ya you bind it but that is showing the error because of the wrong setup
I have already bound custom-user service in application.ts . See here (https://github.com/pratikjaiswal15/loopbackauthrization/blob/master/src/application.ts#L77)
Please look at the points I mention in the above comment and my implementation is almost the same.
It is not the wrong setup. Actually I changed data types in the user and user-credential model in npm package authentication/jwt. That is the reason you were getting errors.
Anyway, the actual issue is authentication not sending role property to authorization.
Hey, @pratikjaiswal15 Can you tell me where are you creating the jwt strategy. I am not able to find in your project.
Hey, @pratikjaiswal15 Can you tell me where are you creating the jwt strategy. I am not able to find in your project.
Looks like it's a new built in one: https://github.com/pratikjaiswal15/loopbackauthrization/blob/50c5cccb2920e6740b3e5778534ac7db72e364c3/src/application.ts#L3
See extension source.
Hey, @pratikjaiswal15 Can you tell me where are you creating the jwt strategy. I am not able to find in your project.
Looks like it's a new built in one: https://github.com/pratikjaiswal15/loopbackauthrization/blob/50c5cccb2920e6740b3e5778534ac7db72e364c3/src/application.ts#L3
Yes
@dougal83 can you please tell why property role is not getting passed from authentication to authorization?
Thanks @dougal83 and I think in this token role is not included
https://github.com/strongloop/loopback-next/blob/927789e254f4db7945201609683ae83a18f1c48d/examples/access-control-migration/src/components/jwt-authentication/services/jwt.service.ts#L54
So to include role you can create your own jwt strategy
only these property you will get in a token which are

you can look at this https://github.com/HrithikMittal/loopback4-authorization/blob/2f7f04efb7c00580acccd7ac145c7a5f1fb44702/src/services/user-service.ts#L36

There is custom user service which does same. Here
https://github.com/pratikjaiswal15/loopbackauthrization/blob/50c5cccb2920e6740b3e5778534ac7db72e364c3/src/services/custom-user.service.ts#L49
The best person to answer questions on this is @jannyHou who has been working on this brand new component. Janny, how would you recommend to proceed?
Yes @pratikjaiswal15 you are right you are converting into profile object where you add everything like this
const profile = {
[securityId]: user.id!.toString(),
name: user.name,
id: user.id,
email: user.email,
role: user.role,
address: user.address
}
after this, you are sending this to generate token right?
and for this, you are using the predefined method of @loopback/authentication-jwt which will take only these parameters to create a token
const userInfoForToken = {
id: userProfile[securityId],
name: userProfile.name,
email: userProfile.email,
};
But you need role also in your token for that you can add it by your own.
Oh yes. Let me try this
Hey, @pratikjaiswal15 Can you tell me where are you creating the jwt strategy. I am not able to find in your project.
Looks like it's a new built in one: https://github.com/pratikjaiswal15/loopbackauthrization/blob/50c5cccb2920e6740b3e5778534ac7db72e364c3/src/application.ts#L3
See extension source.
@dougal83 : He has bind his custom strategy here
https://github.com/pratikjaiswal15/loopbackauthrization/blob/50c5cccb2920e6740b3e5778534ac7db72e364c3/src/application.ts#L76-L77
Hi guys lets catch up quickly. I was not hoping it to be so long :100:
Well @pratikjaiswal15 3 quick question to you:
1) Where you have defined your roles.
2) Where at the time of user creation you have bind the roles with user.
3) Are you using some sort of permissions model.
No still not working.
As per shopping example role should be in authorizationCtx.principals[0]https://github.com/strongloop/loopback4-example-shopping/blob/d0822a31bed831504b8655bc057c0de6473ece09/packages/shopping/src/services/basic.authorizor.ts#L26
But in my case authorizationCtx.principals[0] contains only id and name
As per shopping example role should be in
authorizationCtx.principals[0]https://github.com/strongloop/loopback4-example-shopping/blob/d0822a31bed831504b8655bc057c0de6473ece09/packages/shopping/src/services/basic.authorizor.ts#L26But in my case
authorizationCtx.principals[0]contains only id and name
Ya you have to add that what we discuss above and all the part which are mentioned by @madaky
Where you have defined your roles.
Where at the time of user creation you have bind the roles with user.
Are you using some sort of permissions model.
No. I have property role in user model. And while making post request to user from frontend or loopback explorer I am specifing property role as admin or user.
Like this
{ "name " : "abc",
"Email : "[email protected]",
"role" : " admin"
}
But you don't have password property in the user model.
@HrithikMittal password property is stored with another model called user-credential which has has one relationship with user model
@madaky
- Where you have defined your roles.
- Where at the time of user creation you have bind the roles with user.
- Are you using some sort of permissions model.
Answers
@pratikjaiswal15 : Can you clean up code. If possible rebuild the code from starting. and you will get it resolved.
As per our discussion with @pratikjaiswal15 with the issue https://github.com/pratikjaiswal15/loopbackauthrization/issues/1?notification_referrer_id=MDE4Ok5vdGlmaWNhdGlvblRocmVhZDkwMjE1MjQyMjo0MjA3NTAyMw%3D%3D#issuecomment-636134643
Now you can close this issue.
Finally, I created my own token service. And it resolved the issue.
Yes @pratikjaiswal15 you are right you are converting into profile object where you add everything like this
const profile = { [securityId]: user.id!.toString(), name: user.name, id: user.id, email: user.email, role: user.role, address: user.address }after this, you are sending this to generate token right?
and for this, you are using the predefined method of@loopback/authentication-jwtwhich will take only these parameters to create a tokenconst userInfoForToken = { id: userProfile[securityId], name: userProfile.name, email: userProfile.email, };But you need role also in your token for that you can add it by your own.
" for that you can add it by your own. " -- Could you please give guidance on how to do this?
@ianjenkinsii You can have your own jwt.service.ts and bind it like this.
this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(
JWTService,
);
Most helpful comment
The best person to answer questions on this is @jannyHou who has been working on this brand new component. Janny, how would you recommend to proceed?