/// <inheritDoc/> protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { // Add custom username header if it doesn't exist already. if (!request.Headers.Contains(HmacApiAuthConfiguration.UsernameHeader)) { request.Headers.Add(HmacApiAuthConfiguration.UsernameHeader, Username); } // Set the Date field in the header. request.Headers.Date = DateTimeOffset.Now; // Build out the string representation of the request. var representation = _representationBuilder.BuildRequestRepresentation(request); // Retrieve the user secret key from the secrets repository. var secret = _secretRepository.GetSecretForUser(Username); // Calculate the digital signature. string signature = _signatureCalculator.Signature(secret, representation); // Add signature to the header of the request message. var header = new AuthenticationHeaderValue(HmacApiAuthConfiguration.AuthenticationScheme, signature); request.Headers.Authorization = header; // Call inner message handler for response. return(base.SendAsync(request, cancellationToken)); }
/// <summary> /// Validates the HMAC digital signature of the API request. /// </summary> /// <param name="requestMessage">The API request message to be validated for authenticity.</param> /// <returns><c>true</c> if authenticated successfully, <c>false</c> otherwise.</returns> protected async Task <bool> IsAuthenticated(HttpRequestMessage requestMessage) { // No username, no authentication. if (!requestMessage.Headers.Contains(HmacApiAuthConfiguration.UsernameHeader)) { return(false); } // No date, no authentication. if (!IsDateValid(requestMessage)) { return(false); } // No Authorization header or wrong type, no authentication. if (requestMessage.Headers.Authorization == null || requestMessage.Headers.Authorization.Scheme != HmacApiAuthConfiguration.AuthenticationScheme) { return(false); } // No ContentMD5 header yet there is Content available, no authentication. // No match between ContentMD5 header and actual Content MD5 hash, no authentication. if (!await IsMd5Valid(requestMessage)) { return(false); } // Using the user's key we will now calculate what the HMAC signature should be based on the provided request message. // After building our own copy of the signature we will compare it to the signature provided in the API request. // Retrieve the username and validate user has a registered key. string username = requestMessage.Headers.GetValues(HmacApiAuthConfiguration.UsernameHeader).First(); var secret = _secretRepository.GetSecretForUser(username); if (secret == null) { return(false); } // Build string representation of request. The client should have built their request the same way. var representation = _representationBuilder.BuildRequestRepresentation(requestMessage); if (representation == null) { return(false); } // Calculate our version of the HMAC signature and compare it to the request message signature to validate authentication. var signature = _signatureCalculator.Signature(secret, representation); var result = requestMessage.Headers.Authorization.Parameter == signature; return(result); }
protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { string reqSignature = ""; HttpResponseMessage response = null; // Get digital signature if available. if (request.Headers?.Authorization != null && request.Headers?.Authorization.Scheme == HmacApiAuthConfiguration.AuthenticationScheme) { reqSignature = request.Headers.Authorization.Parameter; } else { // We should just return here if there is no signature. return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Unauthorized request : Missing or invalid signature")); } // TODO : Check to see if signature is currently in cache. If so return now. // TODO : Cache signature in memory for the validity period (5 mins) to ensure no request gets replayed. try { // Call the base authentication handler. response = await base.SendAsync(request, cancellationToken); } catch (Exception exception) { // Catch any authentication error messages and provide custom error message response. response = request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Error authenticating"); var respMsg = new StringBuilder(); respMsg.AppendLine("Error authenticating..."); respMsg.AppendLine("StatusCode : " + response.StatusCode); respMsg.AppendLine("ReasonPhrase : " + response.ReasonPhrase); respMsg.AppendLine("WwwAuthenticate : " + response.Headers.WwwAuthenticate.FirstOrDefault().ToString()); respMsg.AppendLine("RequestDate : " + request.Headers.Date.GetValueOrDefault().UtcDateTime.ToString(CultureInfo.InvariantCulture)); respMsg.AppendLine("ServerDate : " + (DateTimeOffset.Now)); respMsg.AppendLine(); respMsg.AppendLine("ExceptionMessage : " + exception.Message); respMsg.AppendLine("ExceptionSource : " + exception.Source); respMsg.AppendLine("ExceptionInnerMessage : " + exception.InnerException?.Message); respMsg.AppendLine("ExceptionStackTrace : " + exception.StackTrace); response.Content = new StringContent(respMsg.ToString()); } // Catch any authentication failure message and provide custom error message response. if (response != null && response.StatusCode == HttpStatusCode.Unauthorized) { var serverDate = DateTimeOffset.Now; var respMsg = new StringBuilder(); respMsg.AppendLine("Authentication failed"); respMsg.AppendLine(); respMsg.AppendLine("Basic Details\n"); respMsg.AppendLine("URL : " + request.RequestUri.AbsoluteUri.ToLower()); respMsg.AppendLine("StatusCode : " + response.StatusCode); respMsg.AppendLine("ReasonPhrase : " + response.ReasonPhrase); //respMsg.AppendLine("WwwAuthenticate : " + response.Headers.WwwAuthenticate.FirstOrDefault().ToString()); respMsg.AppendLine("RequestDate : " + request.Headers.Date.GetValueOrDefault().ToString("r")); respMsg.AppendLine("ServerDate : " + serverDate.ToString("r")); respMsg.AppendLine("DateDifference : " + (serverDate - request.Headers.Date.GetValueOrDefault())); respMsg.AppendLine(); string username = ""; if (request.Headers.Contains(HmacApiAuthConfiguration.UsernameHeader)) { username = request.Headers.GetValues(HmacApiAuthConfiguration.UsernameHeader).First(); } string signature = ""; if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme == HmacApiAuthConfiguration.AuthenticationScheme) { signature = request.Headers.Authorization.Parameter; } string md5 = ""; string serverMd5 = ""; long? contentLength = 0; if (request.Content != null) { contentLength = request.Content.Headers.ContentLength; serverMd5 = Convert.ToBase64String(await MD5Helper.ComputeHash(request.Content)) == "1B2M2Y8AsgTpgAmY7PhCfg==" ? "" : Convert.ToBase64String(await MD5Helper.ComputeHash(request.Content)); if (request.Content.Headers.ContentMD5 != null && request.Content.Headers.ContentMD5.Length > 0) { md5 = Convert.ToBase64String(request.Content.Headers.ContentMD5); } } bool validRequest = IsRequestValid(request); string msgSigRep = _representBuilder.BuildRequestRepresentation(request); string serverSignature = _sigCalc.Signature(_secretRepo.GetSecretForUser(username), msgSigRep); respMsg.AppendLine("Auth Details\n"); respMsg.AppendLine("RequestValid : " + validRequest.ToString()); respMsg.AppendLine("Username : "******"ApiKey : " + _secretRepo.GetSecretForUser(username)); respMsg.AppendLine("Signature : " + signature); respMsg.AppendLine("ServerSignature : " + serverSignature); respMsg.AppendLine(); respMsg.AppendLine("Content Details\n"); respMsg.AppendLine("ContentMd5 : " + md5); respMsg.AppendLine("ServerContentMd5 : " + serverMd5); respMsg.AppendLine("CannonicalRep :\n" + msgSigRep); respMsg.AppendLine("ContentLength : " + contentLength); respMsg.AppendLine("ContentType : " + request.Content.Headers.ContentType); respMsg.AppendLine("ContentMediaType : " + request.Content.Headers.ContentType.MediaType.ToLower()); respMsg.AppendLine("Content : \"" + await request.Content.ReadAsStringAsync() + "\""); response.Content = new StringContent(respMsg.ToString()); } return(response); }