I am receiving webhooks from a woocommerce site into a nodejs/express application. I am trying to verify the webhook's signature to prove authenticity, yet the hash I compute never corresponds to the signature that woocommerce reports in the hook's signature header.
Here is the code I am using to verify the authenticity:
function verifySignature(signature, payload, key){
var computedSignature = crypto.createHmac("sha256", key).update(payload).digest('base64');
debug('computed signature: %s', computedSignature);
return computedSignature === signature;
}
This function is being called with the following parameters:
var signature = req.headers['x-wc-webhook-signature'];
verifySignature(signature, JSON.stringify(req.body), config.wooCommence.accounts.api[config.env].webhookSecret)
The webhook's signature headers reports the signature as BewIV/zZMbmuJkHaUwaQxjX8yR6jRktPZQN9j2+67Oo=. The result of the above operation, however, is S34YqftH1R8F4uH4Ya2BSM1rn0H9NiqEA2Nr7W1CWZs=
I have manually configured the secret on the webhook, and as you see in the code above, this same secret is also hardcoded in the express application. So either I am taking the wrong payload to compute the signature, or there is something else fishy that prevents me from verifying these signature.
Would appreciate any pointers to help me solve this issue.
For people using node, this should do the trick.
var processWebHookSignature = function (secret, body, signature) {
signatureComputed = crypto.createHmac('SHA256', secret).update(
new Buffer(JSON.stringify(body), 'utf8')).digest('base64');
return ( signatureComputed === signature ) ? true : false;
}
Since this is the top Google result for this question and there isn't a complete answer out there, here's a Python version using Flask that validates the WooCommerce webhook signature. It took a bit of trial and error, hope it helps someone out there:
import json
import base64
import hmac
import hashlib
from flask import Flask, request, Response
app = Flask(__name__)
# The WooCommerce webhook secret
WEBHOOK_SECRET = 'abc123456'
# Function that compares the computed signature to the one in the request
def verify_woocommerce_signature(body, signature, secret):
digest = hmac.new(bytes(secret, 'utf-8'), body, hashlib.sha256).digest()
encoded = base64.b64encode(digest).decode()
return encoded == signature
# WooCommerce Order Creation Event
#app.route('/webhooks/woocommerce/order_created', methods=['POST'])
def webhooks_woocommerce_order_created():
# Get raw request body
body = request.get_data()
# Get request signature
signature = request.headers['X-WC-WEBHOOK-SIGNATURE']
# Verify webhook signature and handle mismatch
if verify_woocommerce_signature(body, signature, WEBHOOK_SECRET) is False:
msg = {"success": False}
return Response(json.dumps(msg), status=400, mimetype='application/json')
# Signatures match, process the payload
Old question but maybe it helps some poor soul out there.
The signature needs to be checked against the body and not the JSON it contains. i.e. the raw bytes of the body.
pseudo:
byte[] body = request.Body;
string signature = request.Header["X-WC-Webhook-Signature"];
byte[] secretUtf8 = GetUtf8Bytes("yoursecrethere");
byte[] hash = HMAC_SHA256.ComputeHash(body, secretUtf8);
string hashBase64 = ToBase64String(hash);
bool isValid = hashBase64 == signature;
I stumbled upon this while searching for a solution to have an Asp.NET application check signature of the Woocommerce web hook. My answer is based on the pseudo code Johannes provided which worked great. I implemented a custom controller attribute to intercept the request and check the signature before it hits the API controller method:
public class HmacSignatureFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var requestContent = actionContext.Request.Content;
var jsonContent = requestContent.ReadAsStringAsync().Result;
var byteContent = requestContent.ReadAsByteArrayAsync().Result;
//if the request contains this, it's the verification request from Woocommerce
//when the webhook is created so let it pass through so it can be verified
if (!jsonContent.Contains("webhook_id"))
{
var requestSignature = actionContext.Request.Headers;
var bodyHash = HashHMAC("test", byteContent); //this is the shared key between Woo and custom API. should be from config or database table.
var signature = actionContext.Request.Headers.GetValues("x-wc-webhook-signature");
if (bodyHash != signature.FirstOrDefault())
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
}
base.OnActionExecuting(actionContext);
}
private static string HashHMAC(string key, byte[] message)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
var hash = new HMACSHA256(keyBytes);
var computedHash = hash.ComputeHash(message);
return Convert.ToBase64String(computedHash);
}
}
Then to use the filter in your Api controller:
[RoutePrefix("api/woo")]
public class WooController : ApiController
{
public SomeService _service;
public WooController()
{
this._service = new SomeService();
}
// POST api/values
[Route("orderCreated")]
[HttpPost]
[HmacSignatureFilter]
public string Post()
{
var requestContent = Request.Content;
var jsonContent = requestContent.ReadAsStringAsync().Result;
//this is the test request from Woocommerce. Don't do anything but
//respond so it can verify the endpoint
if (jsonContent.Contains("webhook_id"))
{
return "Webhook Test Success";
}
var wooOrder = JsonConvert.DeserializeObject<WooOrderModel>(jsonContent);
//call a service to use the order data provided by WooCommerce
_service.AddOrder(wooOrder);
return "Success";
}
}
Note: Hashing code was referenced from this SO post.
SOLVED in TypeScript. I added this in the server.ts:
this.app.use(bodyParser.json({
verify: function(req, res, buf) {
(req as any).rawBody = buf;
}
}));
and than:
const computedSignature = crypto.createHmac("sha256", process.env.WOOCOMMERCE_SECRET).update((req as any).rawBody).digest("base64");
Hash must be calculated over the 'raw body'. When used in an 'express application' and using JSON bodyParser middleware 'raw body' is lost, see How to access the raw body of the request before bodyparser? to hold-on to the 'raw body'.
For example:
// 'misuse' verify option
app.use(bodyParser.json({
verify: function(req,res,buf) {
req.rawBody=buf;
}
}));
var wcSignature = req.get('X-Wc-Webhook-Signature');
debug('wc signature: %s', wcSignature);
var calculatedSignature = crypto.createHmac('SHA256', secret)
.update(req.rawBody, 'utf8')
.digest('base64');
debug('calculated signature: %s', calculatedSignature);
Hope to save someone time, below works for me.
// Make sure to add a WISTIA_SECRET_KEY in your Environment Variables
// See https://docs.pipedream.com/environment-variables/
const secret = process.env.SELF_AUTOMATE_KEY;
const signature = event.headers["x-wc-webhook-signature"];
const body = steps.trigger.raw_event["body_b64"];
const clean_Body = body.replace("body_b64: ", "");
//const body = steps.trigger.raw_event;
console.log(event.headers["x-wc-webhook-signature"]);
console.log("Print Body", clean_Body);
if (process.env.SELF_AUTOMATE_KEY === undefined) {
$end("No WISTIA_SECRET_KEY environment variable defined. Exiting.")
}
if (!("x-wc-webhook-signature" in event.headers)) {
$end("No x-wc-webhook-signature header present in the request. Exiting.")
}
// Once we've confirmed we have a signature, we want to
// validate it by generating an HMAC SHA-256 hexdigest
const crypto = require('crypto');
const hash = crypto.createHmac('sha256',
secret).update(JSON.stringify(clean_Body), 'base64').digest('base64');
console.log(hash);
// $end() ends the execution of a pipeline, presenting a nice message in the "Messages"
// column in the inspector above. See https://docs.pipedream.com/notebook/code/#end
if (hash !== signature) {
$end("The correct secret key was not passed in the event. Exiting!")
}
SOLVED IN C# API. For people using it as an Attribute in a C# API for a controller, I made a good solution with multiple threads on StackOverflow:
[AttributeUsage(AttributeTargets.Method)]
public class ShaKeyAttribute : Attribute, IAuthorizationFilter
{
private readonly ShaAuthOptions _options;
private readonly ILogger<ShaKeyAttribute> _log;
public ShaKeyAttribute(IOptions<ShaAuthOptions> options, ILogger<ShaKeyAttribute> log)
{
this._options = options.Value;
this._log = log;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
this._log.LogInformation("Entering in Sha Auth...");
if (!context.HttpContext.Request.Headers.TryGetValue("x-wc-webhook-signature", out var extractedSignature))
{
this._log.LogError("Sha Auth failed. Signature was not provided");
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Signature was not provided",
};
return;
}
// This logic for body rewind comes from: https://stackoverflow.com/a/40994711/14010438
string bodyString;
var req = context.HttpContext.Request;
// Allows using several time the stream in ASP.Net Core
req.EnableBuffering();
// Arguments: Stream, Encoding, detect encoding, buffer size
// AND, the most important: keep stream opened
using (StreamReader reader
= new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
{
bodyString = reader.ReadToEnd();
}
// Rewind, so the core is not lost when it looks the body for the request
req.Body.Position = 0;
// From this point, DON'T TOUCH THE REQUEST BODY. Instead, use bodyString
// https://stackoverflow.com/a/62032738/14010438
byte[] requestData = Encoding.UTF8.GetBytes(bodyString);
var encoding = new UTF8Encoding();
var key = this._options.Secret;
var keyBytes = encoding.GetBytes(key);
var hash = new HMACSHA256(keyBytes);
var computedHash = hash.ComputeHash(requestData);
var computedHashString = Convert.ToBase64String(computedHash);
if (extractedSignature != computedHashString)
{
this._log.LogError("Sha Auth failed. Signature is not valid");
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Signature is not valid",
};
}
this._log.LogInformation("Successfully passed Sha Auth.");
}
}
You also need to add this in your startup.cs:
// https://stackoverflow.com/questions/47735133/asp-net-core-synchronous-operations-are-disallowed-call-writeasync-or-set-all
// Needed for ShaAuth
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
Here is my controller:
[AllowAnonymous]
[HttpPost]
[ServiceFilter(typeof(ShaKeyAttribute))]
public async Task<IActionResult> Create([FromBody] WooCommerceRequest request)
{
this._log.LogInformation("Executing Create of Subscription controller...");
var response = await this._mediator.Send(new AddSubscriptionCommand() { OrderId = request.OrderId });
this._log.LogInformation("Finished executing Create of Subscription controller.");
return this.Ok();
}
Hope this help somebody.
Related
how do i get the user and password informed in basic authentication in azure functions ISOLATED?
exemple, using the app SOAPUI to make a call to the function:
[https://i.imgur.com/3u7eymT.png]
how do i get in the function this USER and his password ?
[Function("Function1")]
public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
{
_logger.LogInformation("C# HTTP trigger function processed a request.");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
response.WriteString("Welcome to Azure Functions!");
return response;
}
i also have a middleware how i get this info in him too?
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
i tried to search in the header, or the identities but i can't find this info/login and password
For Basic Authentication we need to change the open API security property values as below
[OpenApiSecurity("basic_auth", SecuritySchemeType.Http, Scheme = OpenApiSecuritySchemeType.Basic)]
Below is the screenshot of SoapUI
Authorization header need to be added in SoapUI as shown below
Code In Function
var headers = req.Headers["Authorization"];
if (ValidateToken(headers))
{
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name) ? "Pass a name in the query string" : $"{name}";
return new OkObjectResult(responseMessage);
}
Validation Code part
string encode_Credentials = header.Substring("Basic ".Length).Trim();
Encoding encode = Encoding.GetEncoding("iso-8859-1");
string credentials = encode.GetString(Convert.FromBase64String(encode_Credentials));
int seperatorIndex = credentials.IndexOf(':');
var username = credentials.Substring(0, seperatorIndex);
var password = credentials.Substring(seperatorIndex + 1);
if (username is "Rajesh" && password is "1142") return true;
else return false;
I am using this great method, found in this article https://codingstill.com/2016/01/verify-jwt-token-signed-with-rs256-using-the-public-key/#comment-3232, to validate my Azure idToken, which is signed with RS256.
I noticed that Azure AD changes the public key in a period of time. By reading this article, https://nicksnettravels.builttoroam.com/post-2017-01-24-verifying-azure-active-directory-jwt-tokens-aspx. Therefore, I used HttpClient (c#) to obtain the public key from the URL below, every time when I need to validate the token.
https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}
I did get the result of the public key, which is a string array in x5c key. Below is my code:
public static string GetPublicKeyAsync(string kid)
{
using (var client = new HttpClient())
{
HttpResponseMessage response = client.GetAsync("https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}").Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
// by calling .Result you are synchronously reading the result
string responseBody = responseContent.ReadAsStringAsync().Result;
JObject json = JObject.Parse(responseBody);
string c = json["keys"].ToString();
List<ADPublic> allPublicKeys = JsonConvert.DeserializeObject<List<ADPublic>>(json["keys"].ToString());
foreach (ADPublic key in allPublicKeys)
{
if (key.kid == kid)
{
string certificateString = key.x5c[0];
var certificate = new X509Certificate2(Convert.FromBase64String(certificateString));
string pkey = Convert.ToBase64String(certificate.PublicKey.EncodedKeyValue.RawData);
var x509SecurityKey = new X509SecurityKey(certificate)
{
KeyId = key.kid
};
return pkey;
}
}
}
return null;
}
}
When I pass my public key over to the validation method. I always get an error at the line (PublicKeyFactory.CreateKey(keyBytes) method ):
Please refer to the verify method I mentioned in the beginning of this question for the code below:
// call my get public key function...
string key = GetPublicKeyAsync(headerData["kid"].ToString());
var keyBytes = Convert.FromBase64String(key); // your key here
**AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);**
Unknown object in GetInstance: Org.BouncyCastle.Asn1.DerInteger Parameter name: obj
I think I am almost there, but missing the last part, could you please help? Thank you very much!
I got the same error when using certificate.PublicKey.EncodedKeyValue.RawData to get the public key. I referred to this issue and finally succeeded.
The code shows getting AsymmetricKeyParameter from x5c string. Decode(...) function is referred to in the article.
string token = "<the token that you want to verify>";
string certificateString = "<the first x5c of the first key from https://login.microsoftonline.com/{TenantId}/discovery/v2.0/keys?appid={AppId}>";
byte[] buffer = Convert.FromBase64String(certificateString);
X509CertificateParser parser = new X509CertificateParser();
var _certificate = parser.ReadCertificate(buffer);
AsymmetricKeyParameter publicKey = _certificate.GetPublicKey();
string result = Decode(token, publicKey);
Console.WriteLine("result: " + result);
I see this code within a custom owin handler to do Oauth2. For example here: https://github.com/RockstarLabs/OwinOAuthProviders/blob/master/Owin.Security.Providers/Reddit/RedditAuthenticationHandler.cs
Can someone explain to me in plain English what these two methods do in the context of oauth2? It seems to be related to CSRF but not sure how.
When a redirect to an "OAuth 2" partner occurs there must be someway of correlating the eventual redirect back to your own application with the original redirect that you sent.
The way the Microsoft.Owin AuthenticationHandler accomplishes this:
generates a nonce of random bytes and retains it in a browser cookie
(GenerateCorrelationId)
encrypts this nonce and other information and your job is to pass this in a state query string parameter to the partner (recall that the partner's job is to return this value right back to your application after authenticating the user)
validates the nonce by decrypting the state query string parameter and verifying it matches the value in the cookie stored (ValidateCorrelationId)
Here is the source:
protected void GenerateCorrelationId(AuthenticationProperties properties)
{
if (properties == null)
{
throw new ArgumentNullException("properties");
}
string correlationKey = Constants.CorrelationPrefix +
BaseOptions.AuthenticationType;
var nonceBytes = new byte[32];
Random.GetBytes(nonceBytes);
string correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
properties.Dictionary[correlationKey] = correlationId;
Response.Cookies.Append(correlationKey, correlationId, cookieOptions);
}
protected bool ValidateCorrelationId(AuthenticationProperties properties,
ILogger logger)
{
if (properties == null)
{
throw new ArgumentNullException("properties");
}
string correlationKey = Constants.CorrelationPrefix +
BaseOptions.AuthenticationType;
string correlationCookie = Request.Cookies[correlationKey];
if (string.IsNullOrWhiteSpace(correlationCookie))
{
logger.WriteWarning("{0} cookie not found.", correlationKey);
return false;
}
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
Response.Cookies.Delete(correlationKey, cookieOptions);
string correlationExtra;
if (!properties.Dictionary.TryGetValue(
correlationKey,
out correlationExtra))
{
logger.WriteWarning("{0} state property not found.", correlationKey);
return false;
}
properties.Dictionary.Remove(correlationKey);
if (!string.Equals(correlationCookie, correlationExtra, StringComparison.Ordinal))
{
logger.WriteWarning("{0} correlation cookie and state property mismatch.",
correlationKey);
return false;
}
return true;
}
I want to call Api function (1st) . from 2nd Api function using HttpClient. But I always get 404 Error.
1st Api Function (EndPoint : http : // localhost : xxxxx /api/Test/)
public HttpResponseMessage Put(int id, int accountId, byte[] content)
[...]
2nd Api function
public HttpResponseMessage Put(int id, int aid, byte[] filecontent)
{
WebRequestHandler handler = new WebRequestHandler()
{
AllowAutoRedirect = false,
UseProxy = false
};
using (HttpClient client = new HttpClient(handler))
{
client.BaseAddress = new Uri("http://localhost:xxxxx/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var param = new object[6];
param[0] = id;
param[1] = "/";
param[2] = "?aid=";
param[3] = aid;
param[4] = "&content=";
param[5] = filecontent;
using (HttpResponseMessage response = client.PutAsJsonAsync("api/Test/", param).Result)
{
return response.EnsureSuccessStatusCode();
}
}
}
So My question is that. Can I post Method Parameter as an object array from HttpClient as I did ? I don't want to Pass model as method parameter.
What is the wrong in my code ?
Unable to get any response , after change code to
return client.PutAsJsonAsync(uri, filecontent)
.ContinueWith<HttpResponseMessage>
(
task => task.Result.EnsureSuccessStatusCode()
);
OR
return client.PutAsJsonAsync(uri, filecontent)
.ContinueWith
(
task => task.Result.EnsureSuccessStatusCode()
);
As you probably found out, no you can't. When you call PostAsJsonAsync, the code will convert the parameter to JSON and send it in the request body. Your parameter is a JSON array which will look something like the array below:
[1,"/","?aid",345,"&content=","aGVsbG8gd29ybGQ="]
Which isn't what the first function is expecting (at least that's what I imagine, since you haven't showed the route info). There are a couple of problems here:
By default, parameters of type byte[] (reference types) are passed in the body of the request, not in the URI (unless you explicitly tag the parameter with the [FromUri] attribute).
The other parameters (again, based on my guess about your route) need to be part of the URI, not the body.
The code would look something like this:
var uri = "api/Test/" + id + "/?aid=" + aid;
using (HttpResponseMessage response = client.PutAsJsonAsync(uri, filecontent).Result)
{
return response.EnsureSuccessStatusCode();
}
Now, there's another potential issue with the code above. It's waiting on the network response (that's what happens when you access the .Result property in the Task<HttpResponseMessage> returned by PostAsJsonAsync. Depending on the environment, the worse that can happen is that it may deadlock (waiting on a thread in which the network response will arrive). In the best case this thread will be blocked for the duration of the network call, which is also bad. Consider using the asynchronous mode (awaiting the result, returning a Task<T> in your action) instead, like in the example below
public async Task<HttpResponseMessage> Put(int id, int aid, byte[] filecontent)
{
// ...
var uri = "api/Test/" + id + "/?aid=" + aid;
HttpResponseMessage response = await client.PutAsJsonAsync(uri, filecontent);
return response.EnsureSuccessStatusCode();
}
Or without the async / await keywords:
public Task<HttpResponseMessage> Put(int id, int aid, byte[] filecontent)
{
// ...
var uri = "api/Test/" + id + "/?aid=" + aid;
return client.PutAsJsonAsync(uri, filecontent).ContinueWith<HttpResponseMessage>(
task => task.Result.EnsureSuccessStatusCode());
}
I want to post some request values alongside the multipart-formdata file contents. In the old API you could use PostFileWithRequest:
[Test]
public void Can_POST_upload_file_using_ServiceClient_with_request()
{
IServiceClient client = new JsonServiceClient(ListeningOn);
var uploadFile = new FileInfo("~/TestExistingDir/upload.html".MapProjectPath());
var request = new FileUpload{CustomerId = 123, CustomerName = "Foo"};
var response = client.PostFileWithRequest<FileUploadResponse>(ListeningOn + "/fileuploads", uploadFile, request);
var expectedContents = new StreamReader(uploadFile.OpenRead()).ReadToEnd();
Assert.That(response.FileName, Is.EqualTo(uploadFile.Name));
Assert.That(response.ContentLength, Is.EqualTo(uploadFile.Length));
Assert.That(response.Contents, Is.EqualTo(expectedContents));
Assert.That(response.CustomerName, Is.EqualTo("Foo"));
Assert.That(response.CustomerId, Is.EqualTo(123));
}
I can't find any such method in the new API, nor any overrides on client.Post() which suggest that this is still possible. Does anyone know if this is a feature that was dropped?
Update
As #Mythz points out, the feature wasn't dropped. I had made the mistake of not casting the client:
private IRestClient CreateRestClient()
{
return new JsonServiceClient(WebServiceHostUrl);
}
[Test]
public void Can_WebRequest_POST_upload_binary_file_to_save_new_file()
{
var restClient = (JsonServiceClient)CreateRestClient(); // this cast was missing
var fileToUpload = new FileInfo(#"D:/test/test.avi");
var beforeHash = this.Hash(fileToUpload);
var response = restClient.PostFileWithRequest<FilesResponse>("files/UploadedFiles/", fileToUpload, new TestRequest() { Echo = "Test"});
var uploadedFile = new FileInfo(FilesRootDir + "UploadedFiles/test.avi");
var afterHash = this.Hash(uploadedFile);
Assert.That(beforeHas, Is.EqualTo(afterHash));
}
private string Hash(FileInfo file)
{
using (var md5 = MD5.Create())
{
using (var stream = file.OpenRead())
{
var bytes = md5.ComputeHash(stream);
return BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLower();
}
}
}
None of the old API was removed from the C# Service Clients, only new API's were added.
The way you process an uploaded file inside a service also hasn't changed.