Skip to content

Instantly share code, notes, and snippets.

@rqx110
Created May 25, 2017 02:32
Show Gist options
  • Save rqx110/686291bbaa75a7e5438b6030f82165a5 to your computer and use it in GitHub Desktop.
Save rqx110/686291bbaa75a7e5438b6030f82165a5 to your computer and use it in GitHub Desktop.
HMAC Auth
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.Hmac
{
public static class AuthenticationConstants
{
public const int ValidityPeriodInMinutes = 60;
public const string AuthenticationScheme = "ApiAuth";
public const string PublicHashHeader = "X-ApiAuth-Hash";
}
}
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.Hmac
{
public class CanonicalRepresentationBuilder : IRequestRepresentationBuilder
{
/// <summary>
/// Builds message representation as follows:
/// HTTP METHOD\n +
/// Content-MD5\n +
/// Timestamp\n +
/// Username\n +
/// Request URI
/// </summary>
/// <returns></returns>
public string BuildRequestRepresentation(HttpRequestMessage requestMessage)
{
bool valid = IsRequestValid(requestMessage);
if (!valid)
{
return null;
}
if (!requestMessage.Headers.Date.HasValue)
{
return null;
}
DateTime date = requestMessage.Headers.Date.Value.UtcDateTime;
string md5 = requestMessage.Content?.Headers.ContentMD5 == null ? "" : Convert.ToBase64String(requestMessage.Content.Headers.ContentMD5);
string httpMethod = requestMessage.Method.Method;
if (!requestMessage.Headers.Contains(AuthenticationConstants.PublicHashHeader))
{
return null;
}
string publicHash = requestMessage.Headers.GetValues(AuthenticationConstants.PublicHashHeader).First();
string uri = requestMessage.RequestUri.AbsolutePath.ToLower();
string representation = string.Join("\n", httpMethod, md5, date.ToString(CultureInfo.InvariantCulture), publicHash, uri);
return representation;
}
private bool IsRequestValid(HttpRequestMessage requestMessage)
{
//for simplicity I am omitting headers check (all required headers should be present)
return true;
}
}
}
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Caching;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Results;
using Abp.Dependency;
using Abp.Domain.Repositories;
using MyApp.Users;
using Microsoft.AspNet.Identity;
namespace MyApp.Hmac
{
[AttributeUsage(AttributeTargets.Class)]
public class HmacAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public bool AllowMultiple => false;
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var request = context.Request;
if (ValidateRequestHeaders(request))
{
var user = GetUserFromRequest(request);
if (ValidateRequestSignatureIsUsers(request, user))
{
SetContextPrincipal(context, user);
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], request);
}
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], request);
}
return Task.FromResult(0);
}
private void SetContextPrincipal(HttpAuthenticationContext context, User user)
{
using (var userManager = IocManager.Instance.ResolveAsDisposable<UserManager>())
{
var identity = userManager.Object.CreateIdentity(user, AuthenticationConstants.AuthenticationScheme);
context.Principal = new ClaimsPrincipal(identity);
}
}
private bool ValidateRequestHeaders(HttpRequestMessage request)
{
if (request.Headers.Authorization == null || request.Headers.Authorization.Scheme != AuthenticationConstants.AuthenticationScheme)
{
return false;
}
if (!request.Headers.Contains(AuthenticationConstants.PublicHashHeader))
{
return false;
}
if (request.Headers.GetValues(AuthenticationConstants.PublicHashHeader).First() == null)
{
return false;
}
if (request.Content.Headers.ContentMD5 != null && !IsMd5Valid(request))
{
return false;
}
return IsDateValid(request);
}
private User GetUserFromRequest(HttpRequestMessage request)
{
using (var userRepository = IocManager.Instance.ResolveAsDisposable<IRepository<User, long>>())
{
var publicHash = request.Headers.GetValues(AuthenticationConstants.PublicHashHeader).First();
return userRepository.Object
.FirstOrDefault(
u =>
string.Compare(u.PublicHash, publicHash, StringComparison.InvariantCulture) == 0
&& u.CanUseWebApi
);
}
}
private bool ValidateRequestSignatureIsUsers(HttpRequestMessage request, User user)
{
using (var requestBuilder = IocManager.Instance.ResolveAsDisposable<IRequestRepresentationBuilder>())
using (var signatureCalculator = IocManager.Instance.ResolveAsDisposable<ISignatureCalculator>())
{
var representation = requestBuilder.Object.BuildRequestRepresentation(request);
if (representation == null)
{
return false;
}
var signature = signatureCalculator.Object.CalculateSignature(user.PrivateHash, representation);
if (MemoryCache.Default.Contains(signature))
{
return false;
}
var validSignature = request.Headers.Authorization.Parameter == signature;
if (validSignature)
{
MemoryCache.Default.Add(signature, user.PublicHash, DateTimeOffset.UtcNow.AddMinutes(AuthenticationConstants.ValidityPeriodInMinutes));
}
return validSignature;
}
}
private bool IsMd5Valid(HttpRequestMessage request)
{
var hashHeader = request.Content.Headers.ContentMD5;
if (request.Content == null)
{
return hashHeader == null || hashHeader.Length == 0;
}
var hash = MD5Helper.ComputeHash(request.Content);
return hash.SequenceEqual(hashHeader);
}
private bool IsDateValid(HttpRequestMessage request)
{
if (request.Headers.Date.HasValue == false)
{
return false;
}
var utcNow = DateTime.UtcNow;
var date = request.Headers.Date.Value.UtcDateTime;
if (date >= utcNow.AddMinutes(AuthenticationConstants.ValidityPeriodInMinutes) || date <= utcNow.AddMinutes(-AuthenticationConstants.ValidityPeriodInMinutes))
{
return false;
}
return true;
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
context.Result = new ResultWithChallenge(context.Result);
return Task.FromResult(0);
}
}
}
using System;
using System.Security.Cryptography;
using System.Text;
namespace MyApp.Hmac
{
public class HmacSignatureCalculator : ISignatureCalculator
{
public string CalculateSignature(string secret, string value)
{
var secretBytes = Encoding.UTF8.GetBytes(secret);
var valueBytes = Encoding.UTF8.GetBytes(value);
using (var hmac = new HMACSHA256(secretBytes))
{
var hash = hmac.ComputeHash(valueBytes);
return Convert.ToBase64String(hash);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MyApp.Hmac
{
public class HmacSigningHandler : HttpClientHandler
{
private readonly IRequestRepresentationBuilder representationBuilder;
private readonly ISignatureCalculator signatureCalculator;
public string PublicHash { private get; set; }
public string PrivateHash { private get; set; }
public HmacSigningHandler(
IRequestRepresentationBuilder representationBuilder,
ISignatureCalculator signatureCalculator)
{
this.representationBuilder = representationBuilder;
this.signatureCalculator = signatureCalculator;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains(AuthenticationConstants.PublicHashHeader))
{
request.Headers.Add(AuthenticationConstants.PublicHashHeader, PublicHash);
}
request.Headers.Date = new DateTimeOffset(DateTime.Now, DateTime.Now - DateTime.UtcNow);
var representation = representationBuilder.BuildRequestRepresentation(request);
string signature = signatureCalculator.CalculateSignature(PrivateHash, representation);
var header = new AuthenticationHeaderValue(AuthenticationConstants.AuthenticationScheme, signature);
request.Headers.Authorization = header;
return base.SendAsync(request, cancellationToken);
}
}
}
using System.Net.Http;
namespace MyApp.Hmac
{
public interface IRequestRepresentationBuilder
{
string BuildRequestRepresentation(HttpRequestMessage requestMessage);
}
}
namespace MyApp.Hmac
{
public interface ISignatureCalculator
{
string CalculateSignature(string secret, string value);
}
}
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace MyApp.Hmac
{
public interface IUserHashStore
{
string GetPrivateHash(string publicHash);
Task<string> GetPrivateHashAsync(string publicHash);
}
}
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace MyApp.Hmac
{
public static class MD5Helper
{
public static byte[] ComputeHash(HttpContent httpContent)
{
using (var md5 = MD5.Create())
{
var content = httpContent.ReadAsByteArrayAsync();
Task.WaitAll(content);
return md5.ComputeHash(content.Result);
}
}
}
}
private ServiceResult PostData(string endPoint, object data)
{
try
{
var client = new HttpClient(new RequestContentMd5Handler
{
InnerHandler =
new HmacSigningHandler(new CanonicalRepresentationBuilder(), new HmacSignatureCalculator())
{
PublicHash = DataManager.PublicHash,
PrivateHash = DataManager.PrivateHash
}
});
var param = Newtonsoft.Json.JsonConvert.SerializeObject(data);
var requestContent = new StringContent(param, Encoding.UTF8, "application/json");
var response = AsyncHelpers.RunSync(() => client.PostAsync(new Uri(new Uri(DataManager.ServiceAddress), endPoint), requestContent, CancellationToken.None));
if (response.StatusCode == HttpStatusCode.OK)
{
return ServiceResult.Ok();
}
var responseContent = AsyncHelpers.RunSync(() => response.Content.ReadAsStringAsync());
return ServiceResult.Error(responseContent);
}
catch (Exception ex)
{
return ServiceResult.Error(ex.Message);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.Hmac
{
public class RequestContentMd5Handler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Content != null)
{
var content = request.Content.ReadAsByteArrayAsync();
Task.WaitAll(content);
var md5 = MD5.Create();
request.Content.Headers.ContentMD5 = md5.ComputeHash(content.Result);
}
return base.SendAsync(request, cancellationToken);
}
}
}
using System;
using System.Web.Http;
using Abp.WebApi.Controllers;
using MyApp.Hmac;
using System.Threading.Tasks;
using System.Web.Http.Description;
using Swashbuckle.Swagger.Annotations;
namespace MyApp.Web.Api.Controllers
{
[HmacAuthentication]
public class SampleController : AbpApiController
{
public SampleController()
{
}
}
}
@rqx110
Copy link
Author

rqx110 commented May 25, 2017

also reference to this and this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment