I am trying to access my User model's "roles" properties. I am using the @loopback/authentication-jwt package for JWT authentication.
I have tried to bind a custom JWTService with application.ts as follows:
this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);
The custom JWT Service is as follows:
import {TokenService} from '@loopback/authentication';
import {inject} from '@loopback/context';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {promisify} from 'util';
import {TokenServiceBindings} from '@loopback/authentication-jwt';
const jwt = require('jsonwebtoken');
const signAsync = promisify(jwt.sign);
const verifyAsync = promisify(jwt.verify);
export class JWTService implements TokenService {
constructor(
@inject(TokenServiceBindings.TOKEN_SECRET)
private jwtSecret: string,
@inject(TokenServiceBindings.TOKEN_EXPIRES_IN)
private jwtExpiresIn: string,
) {}
async verifyToken(token: string): Promise<UserProfile> {
if (!token) {
throw new HttpErrors.Unauthorized(
`Error verifying token: 'token' is null`,
);
}
let userProfile: UserProfile;
try {
// decode user profile from token
const decodedToken = await verifyAsync(token, this.jwtSecret);
//Don't copy over token fields 'iat' and 'exp', nor 'email' to the user profile
userProfile = Object.assign(
{[securityId]: '', name: ''},
{
[securityId]: decodedToken.id,
name: decodedToken.name,
id: decodedToken.id,
roles: decodedToken.roles,
},
);
} catch (error) {
throw new HttpErrors.Unauthorized(
`Error verifying token : ${error.message}`,
);
}
return userProfile;
}
async generateToken(userProfile: UserProfile): Promise<string> {
if (!userProfile) {
throw new HttpErrors.Unauthorized(
'Error generating token: userProfile is null',
);
}
const userInfoForToken = {
id: userProfile[securityId],
name: userProfile.name,
roles: userProfile.roles,
};
// Generate a JSON Web Token
let token: string;
try {
token = await signAsync(userInfoForToken, this.jwtSecret, {
expiresIn: Number(this.jwtExpiresIn),
});
} catch (error) {
throw new HttpErrors.Unauthorized(`Error encoding token : ${error}`);
}
return token;
}
}
Now I decorated an endpoint in the following way:
@post('/faqs', {
security: OPERATION_SECURITY_SPEC,
responses: {
'200': {
description: 'Faq model instance',
content: {'application/json': {schema: getModelSchemaRef(Faq)}},
},
},
})
@authenticate('jwt')
@authorize({
allowedRoles: ['faqs'],
voters: [basicAuthorization],
})
But the JWT service never seems to get triggered.
The basicAuthorization of my decorator is as follows:
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> {
// No access if authorization details are missing
let currentUser: UserProfile;
if (authorizationCtx.principals.length > 0) {
const user = _.pick(authorizationCtx.principals[0], [
'id',
'name',
'roles',
]);
currentUser = {[securityId]: user.id, name: user.name, roles: user.roles};
} 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;
}
However, roles are nonexistent here.
How can I solve this?