Created
May 25, 2017 02:32
-
-
Save rqx110/686291bbaa75a7e5438b6030f82165a5 to your computer and use it in GitHub Desktop.
HMAC Auth
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Net.Http; | |
namespace MyApp.Hmac | |
{ | |
public interface IRequestRepresentationBuilder | |
{ | |
string BuildRequestRepresentation(HttpRequestMessage requestMessage); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace MyApp.Hmac | |
{ | |
public interface ISignatureCalculator | |
{ | |
string CalculateSignature(string secret, string value); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Security.Cryptography.X509Certificates; | |
using System.Threading.Tasks; | |
namespace MyApp.Hmac | |
{ | |
public interface IUserHashStore | |
{ | |
string GetPrivateHash(string publicHash); | |
Task<string> GetPrivateHashAsync(string publicHash); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
{ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
also reference to this and this