Page Contents
Users are expected to program policies that enforce access control in two of the following options:
Authorizer
functions- The authorizer functions are applied globally, i.e, they are enforced on all endpoints in the application
Voter
functions- voters are specific for the endpoint that is decorated with it
- multiple voters can be configured for an endpoint
Usually the
authorize
functions are bound through a provider as below
- The
AuthorizationContext
parameter of theauthorize
function contains the current principal (in the example given above,that would be the current user invokingcancelOrder
) and details of the invoked endpoint.- The
AuthorizationMetadata
parameter of theauthorize
function contains all the details provided in the invoked method’s decorator.
- The
class MyAuthorizationProvider implements Provider<Authorizer> {
/**
* @returns an authorizer function
*
*/
value(): Authorizer {
return this.authorize.bind(this);
}
async authorize(
context: AuthorizationContext,
metadata: AuthorizationMetadata,
) {
events.push(context.resource);
if (
context.resource === 'OrderController.prototype.cancelOrder' &&
context.principals[0].name === 'user-01'
) {
return AuthorizationDecision.DENY;
}
return AuthorizationDecision.ALLOW;
}
}
the
authorize
function is then tagged to an application asAuthorizationTags.AUTHORIZER
as below.
export class MyApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// mount authorization component
this.component(AuthorizationComponent);
// bind the authorizer provider
this.bind('authorizationProviders.my-authorizer-provider')
.toProvider(MyAuthorizationProvider)
.tag(AuthorizationTags.AUTHORIZER);
}
}
- This creates a list of
authorize()
functions.- The
authorize(AuthorizationContext, AuthorizationMetadata)
function in the provider class is expected to be called by theAuthorization Interceptor
which is called for every API endpoint decorated with@authorize()
. - The authorize interceptor gets the list of functions tagged with
AuthorizationTags.AUTHORIZER
(and also the voters listed in the@authorize
decorator per endpoint) and calls the functions one after another. - The
authorize()
function is expected to return an object of typeAuthorizationDecision
. If the type returned isAuthorizationDecision.ALLOW
the currentPrincipal
has passed the executedauthorize()
function’s criteria.
- The
Voter functions are directly provided in the decorator of the remote method
async function compareId(
authorizationCtx: AuthorizationContext,
metadata: MyAuthorizationMetadata,
) {
let currentUser: UserProfile;
if (authorizationCtx.principals.length > 0) {
const user = _.pick(authorizationCtx.principals[0], [
'id',
'name',
'email',
]);
return AuthorizationDecision.ALLOW;
} else {
return AuthorizationDecision.DENY;
}
}
@authenticate('jwt')
@authorize({resource: 'order', scopes: ['patch'], voters: [compareId]})
async patchOrders(
@param.path.string('userId') userId: string,
@requestBody() order: Partial<Order>,
@param.query.string('where') where?: Where<Order>,
): Promise<Count> {
return this.userRepo.orders(userId).patch(order, where);
}
In the above example compareId()
is an authorizing function which is provided
as a voter in the decorator for the patchOrders()
method.