loopbackio / loopbackio/loopback-next
Additional properties in JWT token not accessible
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 5.1k
- Forks
- 1.1k
- Ø Merge
- 2 T. 21 Std.
- Gemergte PRs (30 T.)
- 27
Beschreibung
### Describe the bug
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 {
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 {
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 {
// 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 is undefined here.
How can I solve this?
### Logs
_No response_
### Additional information
_No response_
### Reproduction
-
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne mit dem application.ts TOKEN_SERVICE-Binding und verfolge dann den @authenticate('jwt')- und @authorize-Ablauf für den /faqs-Endpunkt, um zu sehen, wie das verifizierte Profil zu authorizationCtx.principals wird. Reproduziere die Anfrage mit einem Token, das roles enthält, und ermittle, an welcher Stelle diese Eigenschaft verloren geht; das Issue ist abgeschlossen, wenn der Autorisierungskontext die erwarteten roles offenlegt oder die unterstützte Einschränkung eindeutig festgestellt ist.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- typescript
- Bereich
- api, authentication, authorization
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Veraltet
- Klarheit
- Muss geklärt werden
- Anfängerfreundlichkeit
- 28/100