Skip to main content

4.3 Secure business services

Following best security practices, you'd always want to secure business services first, to make sure that the user cannot call any operations that they are not allowed to call, and that they don't have access to any data that they are not allowed to see.

In our example application, access to sales orders should be allowed only to internal employees, and to individual or store customers, but not to vendors or other types of users. Moreover, external customers should be able to see only their own sales orders. Let's see how we can implement these security requirements within the business services generated by Xomega.

Restricting access to operation

We'll start by restricting access to the read list operation for sales orders to external users that are not customers.

First, let's open the Resources.resx file under the AdventureWorks.Services.Entities project, and add a message for restricted operations, as follows.

NameValueComment
OperationNotAllowedOperation not allowed.

Don't forget to run the custom tool on the nested Messages.tt file to regenerate message constants.

Now let's open the ReadListAsync method of our SalesOrderService implementation class, and add the following custom code for security checks at the top of the method.

SalesOrderService.cs
// CUSTOM_CODE_START: add namespaces for custom code below
using AdventureWorks.Services.Common.Enumerations;
using Xomega.Framework;
// CUSTOM_CODE_END
...
public partial class SalesOrderService : BaseService, ISalesOrderService
{
...
public virtual async Task<Output<ICollection<SalesOrder_ReadListOutput>>> ReadListAsync(
SalesOrder_ReadListInput_Criteria _criteria, CancellationToken token = default)
{
...
// CUSTOM_CODE_START: add custom security checks for Read operation below
if (!CurrentPrincipal.IsEmployee() && !CurrentPrincipal.IsIndividualCustomer() &&
!CurrentPrincipal.IsStoreContact())
{
currentErrors.CriticalError(ErrorType.Security, Messages.OperationNotAllowed);
}
// CUSTOM_CODE_END
...
}
}

Notice how we are using the CurrentPrincipal member of the service to determine permissions of the current user, and leverage the convenient methods that we added to the SecurityManager class earlier.

note

Since the ErrorType enum is in the Xomega.Framework namespace, which was not used or declared in this generated file, we needed to declare it in the custom section for the namespaces at the top of the file to prevent it from being erased during regeneration.

If the security check fails, we report a critical error of type Security, which will abort the execution and will return the OperationNotAllowed message to the client.

note

When the business services are called via REST API, these security errors will be reported using HTTP status code 403 (Forbidden), following the REST standards.

Restricting access to data

Now, let's restrict the data returned by the ReadListAsync method for external customers to show only their own sales orders.

We need to find a custom code placeholder for additional filter criteria on the source query, and add some custom code that checks if the CurrentPrincipal is a store or individual customer, and then add their associated storeId or personId respectively as additional filter criteria for the sales order's customer, as follows.

public partial class SalesOrderService : BaseService, ISalesOrderService
{
...
public virtual async Task<Output<ICollection<SalesOrder_ReadListOutput>>> ReadListAsync(
SalesOrder_ReadListInput_Criteria _criteria, CancellationToken token = default)
{
...
// CUSTOM_CODE_START: add custom filter criteria to the source query for ReadList operation below
if (CurrentPrincipal.IsStoreContact())
{
int? storeId = CurrentPrincipal.GetStoreId();
src = src.Where(o => o.CustomerObject.StoreObject.BusinessEntityId == storeId);
}
if (CurrentPrincipal.IsIndividualCustomer())
{
int? personId = CurrentPrincipal.GetPersonId();
src = src.Where(o => o.CustomerObject.PersonObject.BusinessEntityId == personId);
}
// CUSTOM_CODE_END
...
}
}

As before, we are using our custom extension methods from SecurityManager to easily retrieve the storeId or personId for the CurrentPrincipal.

caution

To properly secure all business services you'll need to add similar custom security checks to other CRUD operations of the SalesOrderService, and report any security errors as appropriate.