Secure your custom ServiceStack web service

Overview

In case your custom web service works with sensitive data it’s best to protect it and have the service methods execute only for authenticated users. To achieve that you must first implement the logic to protect your web service methods, and then use Sitefinity CMS Identity Server to authenticate and authorize requests to your service.

First part: Protect your web service methods

The Sitefinity CMS API exposes an easy mechanism for securing your web service methods. You can call the RequestBackendUserAuthentication method of the ServiceUtility class, inside your web service logic. For example, this is how to secure the web service we demonstrated in the Implement a web service to delete orphaned user profiles tutorial:

C#
using ServiceStack;
using SitefinityWebApp.DeleteOrphanedUserProfiles.Dto;
using System;
using System.Collections.Generic;
using System.Linq;
using Telerik.Sitefinity.Data;
using Telerik.Sitefinity.Security;
using Telerik.Sitefinity.Security.Model;
using Telerik.Sitefinity.Web.Services;

namespace SitefinityWebApp.DeleteOrphanedUserProfiles.Secured
{
    public class DeleteOrphanedUserProfilesServiceSecured : IService
    {
        // Any means the method accepts all verbs (e.g. GET, POST, PUT, etc.)
        // You can explicitly specify which verb the method supports by naming it "Get", "Post", "Delete", etc.
        public object Any(DeleteOrphanedUserProfilesRequest request)
        {
            // Secure the method for authenticated backend users only
            ServiceUtility.RequestBackendUserAuthentication();

            if (request.UserId == Guid.Empty)
            {
                throw new ArgumentException("Invalid UserId");
            }

            string result;
            var transactionName = string.Concat("DeleteUserProfileById_", Guid.NewGuid());
            bool hasItemsToDelete = DeleteUserProfiles(transactionName, request.UserId);

            if (hasItemsToDelete)
            {
                TransactionManager.CommitTransaction(transactionName);
                result = $"Orphaned Profiles for user with Id {request.UserId} have been deleted";
            }
            else
            {
                result = $"No orphaned profiles for user with Id {request.UserId} have been found";
            }

            return new DeleteOrphanedUserProfilesResponse { Result = result };
        }

        public object Any(DeleteAllOrphanedUserProfilesRequest request)
        {
            // Secure the method for authenticated backend users only
            ServiceUtility.RequestBackendUserAuthentication();

            var transactionName = string.Concat("DeleteAllOrphanedUserProfiles_", Guid.NewGuid());
            string result;
            bool hasItemsToDelete = DeleteUserProfiles(transactionName, Guid.Empty, request.ProviderName);

            if (hasItemsToDelete)
            {
                TransactionManager.CommitTransaction(transactionName);
                result = $"Orphaned Profiles for users in {request.ProviderName} membership provider have been deleted";
            }
            else
            {
                result = $"No orphaned profiles have been found";
            }

            return new DeleteAllOrphanedUserProfilesResponse { Result = result };
        }

        public bool DeleteUserProfiles(string transactionName, Guid userId, string membershipProviderName = "Default")
        {

            var providers = UserProfileManager.ProvidersCollection;
            var hasItemsToDelete = false;
            foreach (var provider in providers)
            {
                var manager = UserProfileManager.GetManager(provider.Name, transactionName);
                var profiles = new List<UserProfile>();
                if (userId != Guid.Empty)
                {
                    profiles = manager.GetUserProfileLinks()
                                         .Where(l => l.UserId == userId)
                                         .Select(l => l.Profile)
                                         .ToList();
                }
                else
                {
                    profiles = manager.GetUserProfileLinks()
                                        .Where(l => l.MembershipManagerInfo.ProviderName == membershipProviderName)
                                        .Select(l => l.Profile)
                                        .ToList();
                }

                foreach (var profile in profiles)
                {
                    if (profile.User == null)
                    {
                        manager.Delete(profile);
                        hasItemsToDelete = true;
                    }
                }
            }
            return hasItemsToDelete;
        }
    }
}

This way, when Sitefinity CMS handles requests to your web service route, it will serve the request only for authenticated backend users. Otherwise Sitefinity CMS returns a status code 403 – Unauthorized.

Second part: Use a token to authenticate when calling your web service

Once you secure your web service using the above described mechanism, Sitefinity CMS serves the requests to the protected methods only if you are logged in as a backend user in the browser you are requesting the web service from.

However, the more common scenario is to call the web service from another client, not in the browser, where you are already logged in to the website backend. When calling the service from an external client you need to obtain a token using valid backend user credentials, and then append this token when calling the web service.

To create a request for access token, use procedure Request access token to call a web service with OAuth2.

Configure Identity Server for external authentication, request an access token and use it when calling your web service

Next you must configure the Identity Server, used by Sitefinity CMS to facilitate authentication from external clients. This is necessary, so you can obtain an access token and use that token when making calls to your web service. See the instructions listed in Request a token by a trusted client for detailed steps on how to configure the Sitefinity CMS IdentityServer settings to support external authentication and how to obtain an access token.

Once you obtain a token, use it when making calls to your web service. The sample, provided in the Request a token by a trusted client article demonstrates how to make a service call and pass the token you obtained. The IdentityServerBearerTokenAuthentication middleware you mapped for your web service route will process the token and validate it. Upon successful validation Sitefinity CMS will process the web service request and return the data.

Want to learn more?
Enhance your Sitefinity skills by enrolling in free training sessions. Become Sitefinity certified through Progress Education Community to strengthen your professional credentials.