ServiceStack Support

Features

All features of ASPSecurityKit are supported equally on ServiceStack. The base library – ASPSecurityKit.dll is agnostic of any web framework and is interface-driven, which means it relies on framework specific libraries to provide implementation of framework related services. A very good example of this is the IRequestService, which encapsulates functionality related to HTTP request/response. Since such functionality depends on the web framework services, IRequestService has no implementation in the base library; its ServiceStack impleentation can be found in RequestService.

Getting started

Learn important concepts and features of ASPSecurityKit for ServiceStack by following this tutorial in which you build a CRM API service from scratch using ASPSecurityKit’s ServiceStack project template.

Live Demo

Check out the live demo of the CRM service we built in this tutorial mentioned above. The source code for the same is available on GitHub.

Project Templates and Source Packages

ASK’s primary goal is to give you a convention based security framework but with flexibility and extensibility so you can replace any part of the framework if you so wish. Which ORM/data access technology one prefers is the question that may differ from one team/person to another and hence ASK goes on to declare even its security models like IUser, IPermit as interfaces, so you can define their implementation the way you want – including the validation, additional properties or even as data/view model.

However, to get you started quickly, a sample source code implementation of these components is provided through source packages.

The ServiceStack’s project template includes the most basic (Essential) of these source packages, which you can initialize as follows:

dotnet new -i ASPSecurityKit.Templates

dotnet new askss -n SuperCRM -o "D:\My Projects\SuperCRM"

ASK also provides a premium source package which additionally comes with source implementation of several commonly needed service operations, coded with best practices of async, managers, dependency injection and unobtrusive graceful error handling. This considerably speeds up developing the prototypes or initial version of the product services.

You can go through this walkthrough to get started with the Premium source package if you have already bought it, or can try it out before buying the same.

The security pipeline

The recommended way to protect ServiceStack’s requests with ASK’s zero-trust security pipeline is by using the global async filter, which is the default when you add the ASPSecurityKitFeature plugin as follows:

appHost.Plugins.Add(new ASPSecurityKitFeature());

Alternatively, you can also use the ProtectAttribute on your service class (or a base service) to enable the protection for only a portion of the application. You’d still need to add the plugin but turn off the async global filter:

using ASPSecurityKit.ServiceStack;

[Protect]
public class ServiceBase : Service
{
    ...
}

However, the attribute based filters in ServiceStack do not support async execution and hence we recommend the other approach.

The zero-trust nature of the pipeline means that all checks are considered applicable to every operation; you selectively disable one or more checks for one or more operations.

While individual attributes are available to denote which check (known as RequestFeature) you don’t need; however, you can also use Feature attribute, especially when you need to indicate multiple checks you don’t need for an operation.

In the following sections, we’ll go through various checks of the pipeline with examples but refrain from providing the conceptual overview and behind the scene technical information which are already covered comprehensively in the security pipeline article.

Authentication

Apply AllowAnonymous attribute on the DTO to allow unauthenticated request to the operation. If request carries a token, it’s validated and identity is initialized upon success. Check out Anonymous Access section for more information.

[DataContract]
[Route("/sign-up", "POST")]
[AllowAnonymous]
public class SignUp : IReturn<BaseRecordResponse<AppUserDetails>>
{
	[DataMember]
	public string Name { get; set; }

	[DataMember]
	public string Email { get; set; }

	[DataMember]
	public string Password { get; set; }

	[DataMember]
	public bool RememberMe { get; set; }
}

Built-in support exists for multiple kinds of tokens, for example:

var result = await this.authSessionProvider.LoginAsync(request.Email, request.Password, false, this.SecuritySettings.LetSuspendedAuthenticate, true);

However, you need to enable the support for cookie explicitly – you can do so by decorating the base DTO with AcceptAuthCookie attribute.

In most cases, we recommend using HMAC tokens for APIs as HMAC provides better security and integrity guarantees and is easily implementable even in SPA clients. Certain relaxations like excluding body from HMAC computation are already available in HMAC scheme to handle such scenarios as uploading files, so there shouldnt' be a need for using cookie authentication with APIs.

  • Use ServiceKey based token if you’re just looking for a simple key-based authentication wherein just attaching an APIKey to the request authenticates the caller. Do follow the best practices to reduce risks involved with the key-based authentication.

To learn more visit Authentication section on the pipeline page and Authentication Schemes page.

You can also check out building credential-based authentication step of the hands-on tutorial.

Activity Data Authorization (ADA)

Activity authorization is the process of authorizing operations (activities) by looking up associated permissions in the user’s permit set. It’s different from role-based authorization in a way that you don’t hard-code roles in code.

However, with ASK’s ADA, not just the activity but also the data sent as input to that activity (request) is also authorized; ADA traverses through the Request DTO, discovering key data (EntityId) fields and authorizing each of them.

(Reading the design guide and how-to docs is mandatory to understand the concepts and power of ADA).

You can also check out building multi-tenant contact management authorized using ADA step of the hands-on tutorial.

var settings = funqContainer.Resolve<IServiceStackSecuritySettings>();
settings.DTOSuffix = "Request";

...

[DataContract]
[Route("/contacts", "POST")]
// The permission for this will be CreateContact after removing the suffix 'Request'
public class CreateContactRequest : Contact, IReturn<BaseRecordResponse<Contact>>
{
}

[DataContract]
[Route("/contacts/{ContactId}", "DELETE")]
// The permission for this will be DeleteContact after removing the suffix 'Request'
public class DeleteContactRequest : IReturn<BaseResponse>
{
	[DataMember]
	public Guid ContactId { get; set; }
}
  • Use AuthPermission attribute to specify an arbitrary, custom permission for a DTO.
[DataContract]
[Route("/permissions", "GET")]
[AuthPermission(PermissionCodes.OrganizePermissions)]
public class GetPermissions : IReturn<BaseListResponse<Permission>>
{
}

[DataContract]
[Route("/permissions", "POST")]
[AuthPermission(PermissionCodes.OrganizePermissions)]
public class AddPermission : Permission, IReturn<BaseRecordResponse<Permission>>
{
}
  • To update the rule that discovers key data field in the DTO across all operations within the app, override the pattern.
// Adding "number" additionally to identify fields like AccountNumber as identifier to be authorized.
ASPSecurityKit.Authorization.EntityIdAuthorizer.IdMemberSelectorRegexPattern = "^.*?(Id|((?<=^)id)|(?i:username|urn|userid|entityid|number))$"
  • To authorize a particular key field – having non-conventional name – without updating the default pattern, use Authorize attribute. On the other hand, use DoNotAuthorize attribute to ignore a particular field from being identified as a key field even though it’s name matches the pattern.
[DataContract]
[Route("/accounts", "POST")]
public class CreateAccount : IReturn<BaseRecordResponse<Account>>
{
	[DataMember]
	// A bank institution can create an account for any customer user within 
	// the system and no bank institution owns any customer and hence we don't 
	// need to authorize this field.
	[DoNotAuthorize]
	public string Username { get; set; }

	[DataMember]
	public double Amount { get; set; }

	[DataMember]
	public Guid? BranchId { get; set; }

	[DataMember]
	// It's a publically accessible configuration option for account types 
	// so no need for authorization to use as reference in account entity.
	[DoNotAuthorize]
	public Guid AccountTypeId { get; set; }
}

[DataContract]
[Route("/transfers", "POST")]
public class Transfer : IReturn<BaseRecordResponse<Transfer>>
{
	[DataMember]
	// Though this has 'Id' as suffix and will be matched 
	// by the default pattern, we're using the Authorize attribute 
	// to override the member name to locate the related references 
	// loader which is written for AccountId.
	[Authorize("AccountId")]
	public Guid FromAccountId { get; set; }

	[DataMember]
	// This serves two purpose:
	// 1. The default pattern doesn't recognize such names as an identifier. 
	//    If you haven't overridden the same, you can instead indicate 
	//    the same intent with Authorize attribute.
	// 2. With the param 'AccountNumber' name, 
	//    you're telling the name by which the related references loader 
	//    to be located for this identifier.
	[Authorize("AccountNumber")]
	public string ToAccountNumber { get; set; }

	[DataMember]
	public double Amount { get; set; }

	[DataMember]
	public string Remarks { get; set; }
}
public class PermitsAuthDefinition : AuthDefinitionBase
{
	public PermitsAuthDefinition(AppDbContext dbContext, IEntityIdAuthorizer entityIdAuthorizer)
		: base(dbContext, entityIdAuthorizer)
	{
	}

	public async Task<AuthResult> IsAuthorizedAsync(GrantUserPermit dto, string permissionCode)
	{
		var result = await this.entityIdAuthorizer.IsAuthorizedAsync(permissionCode,
			new { UserPermitId = dto.Id, dto.UserId, dto.UserPermitGroupId });

		if (result.IsSuccess)
		{
			return await this.entityIdAuthorizer.IsAuthorizedAsync(dto.PermissionCode, new { dto.EntityId });
		}

		return result;
	}
}

Multi-Factor Authentication (MFA)

  • If you don’t need MFA, you need not do anything as until it’s enabled on user, it has no impact.
  • To opt an operation out of MFA check, decorate the DTO with MFANotRequired attribute (or its equivalent RequestFeature value).
[DataContract]
[Route("/self/send-twofactor-authentication-token", "POST")]
// Both MFA and authorization steps aren't required to send MFA token.
[Feature(ApplyTo.Post, RequestFeature.AuthorizationNotRequired, RequestFeature.MFANotRequired)]
public class SendTwoFactorAuthenticationToken : IReturn<BaseResponse>
{
	[DataMember]
	public bool ResendToken { get; set; }
}
  • For an operation that is involved in setting up MFA, you should decorate it with MultiFactorSetting attribute (or its equivalent RequestFeature value). This is because the MFA settings operation shouldn’t be accessible when user is prompted for MFA, but we have a feature called MFA enforcement, which helps you in forcing users to enable MFA before they can proceed with the account – typically needed in enterprise scenarios to harden security of employee accounts.
[DataContract]
[Route("/self/twofactor-settings", "PATCH")]
[Feature(ApplyTo.Patch, RequestFeature.MFASetting, RequestFeature.ActivityAuthorizationNotRequired)]
public class ChangeTwoFactorAuthSettings : IReturn<BaseResponse>
{
	[DataMember]
	public bool? Enabled { get; set; }
}

Learn more in the MFA section on the pipeline page.

Verification

Typically every multi-user web app needs to verify the identity of the user to safely communicate with the user post sign up. It may involve verifying the email or phone number. ASK’s user verification check helps in enforcing such a requirement.

var settings = funqContainer.Resolve<IServiceStackSecuritySettings>();
settings.MustHaveBeenVerified = false;
[DataContract]
[Route("/self/verification-email", "POST")]
[SkipActivityAuthorization]
[VerificationNotRequired]
public class ResendVerificationEmail : IReturn<BaseResponse>
{
}

Learn more in the Authorization section on the pipeline page.

You can also check out building email verification step of the hands-on tutorial.

Cross-Site Scripting (XSS)

XSS validation of input data is enabled by default; to disable it for the whole app, turn off ValidateXss property during plugin setup:

appHost.Plugins.Add(new ASPSecurityKitFeature { ValidateXss = false });

You can also use AllowHtml attribute on specific properties to skip XSS validaition on them.

Learn more in the XSS section on the pipeline page, and also go through this guide to learn about measures beyond input validation that you need for comprehensive XSS mitigation.

Other Checks

Similarly, for checks like password expiration, IP firewall, impersonation, suspension etc. we have either attributes or settings to customize the default behavior.