We needed a way to authenticate our trading partners and didn't like typical authentication choices like OAuth or Basic Auth. The authentication should be secure and yet easy enough for our partners to implement. In a search of such authentication mechanism I came across the idea of request signing / HMAC, just like Amazon is doing and later I found an awesome blog post where it has been done in WebApi. Here's my port of it to ServiceStack.

First off, there's an Encryptor class:

public class Encryptor  
{        
    private readonly Guid _clientId;
    private readonly string _clientSecret;

    private const string _authorizationScheme = "HMAC-SHA256";

    public Encryptor(Guid clientId, string clientSecret)
    {     
        _clientId = clientId;
        _clientSecret = clientSecret;
    }

    public AuthenticationHeaderValue BuildAuthorizationHeader(string absoluteUri, string requestHttpMethod, string requestContent)
    {            
        if (string.IsNullOrEmpty(absoluteUri) || string.IsNullOrEmpty(requestHttpMethod))
        {
            throw new ArgumentException("Must have absoluteUri and requestHttpMethod");
        }            

        string requestUri = HttpUtility.UrlEncode(absoluteUri.ToLower());

        // For POST and PUT
        string requestContentBase64String = string.Empty;
        if (!string.IsNullOrEmpty(requestContent))
        {            
            byte[] content = Encoding.Default.GetBytes(requestContent);
            MD5 md5 = MD5.Create();
            //Hashing the request body, any change in request body will result in different hash, we'll incure message integrity
            byte[] requestContentHash = md5.ComputeHash(content);
            requestContentBase64String = Convert.ToBase64String(requestContentHash);
        }

        //Creating the raw signature string
        string signature = String.Format("{0:N}{1}{2}{3}", _clientId, requestHttpMethod, requestUri, requestContentBase64String);
        byte[] signatureBytes = Encoding.UTF8.GetBytes(signature);
        byte[] clientSecretBytes = Convert.FromBase64String(_clientSecret);
        using (var hmac = new HMACSHA256(clientSecretBytes))
        {
            byte[] hmacBytes = hmac.ComputeHash(signatureBytes);
            string requestSignatureBase64String = Convert.ToBase64String(hmacBytes);
            //Setting the values in the Authorization header using custom scheme (amx)
            return new AuthenticationHeaderValue(_authorizationScheme, string.Format("ClientId=\"{0:N}\",Signature=\"{1}\"", _clientId, requestSignatureBase64String));
        }
    }
}

I chose not to implement time stamps and nonce.

Then there is an AuthProvider class

public class AuthProvider : IAuthProvider  
    {
        private readonly IDataRepository _dataRepository;

        public AuthProvider(IDataRepository repository)
        {
            _dataRepository = repository;
        }

        public Client Authenticate(string rawRequestAuthHeader, string requestAbsoluteUri, string requestHttpMethod, string requestContent, bool verifySignature)
        {     
            AuthenticationHeaderValue requestAuthHeader;
            if (!AuthenticationHeaderValue.TryParse(rawRequestAuthHeader, out requestAuthHeader))
            {
                return null;
            }

            Guid? clientId = requestAuthHeader.GetClientId();
            string signature = requestAuthHeader.GetSignature();
            if (clientId == null || signature.IsNullOrEmpty())
            {
                return null;
            }

            var dataFromDb = _dataRepository.GetClientAccess(clientId.Value);
            if (dataFromDb == null)
            {
                return null;
            }
            var client = new Client
                {
                    ClientId = clientId.Value,
                    StoreId = dataFromDb.StoreId
                };

            if (verifySignature)
            {
                var clientSecret = dataFromDb.ClientSecret;
                var encryption = new Encryptor(clientId.Value, clientSecret);
                AuthenticationHeaderValue recalculatedHeader = encryption.BuildAuthorizationHeader(requestAbsoluteUri,
                    requestHttpMethod, requestContent);
                string recalculatedSignature = recalculatedHeader.GetSignature();

                if (requestAuthHeader.Scheme == recalculatedHeader.Scheme && signature == recalculatedSignature)
                {
                    return client;
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return client;
            }
        }
    }

This class has a dependency on repository that is responsible for retrieving client credentials.

With that I have custom authorization header:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class HmacAuthenticateAttribute : RequestFilterAttribute  
{
    public HmacAuthenticateAttribute(ApplyTo applyTo)
        : base(applyTo)
    {
        this.Priority = (int)RequestFilterPriority.Authenticate;           
    }

    public HmacAuthenticateAttribute()
        : this(ApplyTo.All) { }

    public IAuthProvider Provider { get; set; }
    public IDataRepository DataRepository { get; set; }
    public IConfiguration Configuration { get; set; }

    public override void Execute(IRequest req, IResponse res, object requestDto)
    {
        var requestRawAuthHeader = req.Headers["Authorization"];            
        string requestHttpMethod = req.Verb;
        string requestUri = req.AbsoluteUri;
        string requestContent = req.GetRawBody();

        Client authResult = Provider.Authenticate(requestRawAuthHeader, requestUri, requestHttpMethod, requestContent, Configuration.ConfigurationSection.VerifySignature);
        if (authResult != null)
        {                
            req.Items.Add(Constants.ItemsKey, authResult);
        }
        else
        {                
            res.StatusCode = (int)HttpStatusCode.Unauthorized;                
            res.Write("Not authorized");
            res.EndRequest();                
        }
    }
}

Then I can decorate my service class with this attribute:

[HmacAuthenticate]
public class ApplicationService : Service  
{
        // ..
        // Here we can get an authenticated client
        var client = base.Request.Items[Constants.ItemsKey] as Client;
        // .. and get data

}