XiaoFaye / WooCommerce.NET

A .NET Wrapper for WooCommerce/WordPress REST API
MIT License
393 stars 218 forks source link

woocommerce_api_authentication_error - Invalid Signature - provided signature does not match #59

Closed trungdh closed 7 years ago

trungdh commented 7 years ago

Hello Mr XiaoFaye !

I've been used WoocommerceNET library in my project to communicate between our system and Woocommerce System to call Woocommerce's API. But when I call to any Woocommerce's API, I got error :

"code": "woocommerce_api_authentication_error", "message": "Invalid Signature - provided signature does not match"

Here is my code which I using to call Woocommerce's API from my system :

RestAPI rest = new RestAPI("http://mydomain.com/wc-api/v3/", "ck_xxx", "cs_xxx"); WooCommerceNET.WooCommerce.Legacy.WCObject wc = new WooCommerceNET.WooCommerce.Legacy.WCObject(rest); var aCustomer = await wc.GetCustomerByEmail("testcustomer@gmail.com");

My site is on http, and I already enabled option enable REST API on my site. Would for love to get some help here, so far your library has been in absolute time saver, thanks in advance!

XiaoFaye commented 7 years ago

What is your domain setting in the wordpress? is it http://mydomain.com or http://www.mydomain.com?

trungdh commented 7 years ago

Hi XiaoFaye ! Many thanks for your reply, I really appreciate it ! I was fixed issue about signature dose not match. Because my site used difference between domain and title of url, i used title url for request api. that's reason make my error. But I got another error. when I test for call Woocommerce's API from winform application, it's success and response data to winform application. But when I call that API from web mvc application, I got error : "object reference not be set to an instance of an object" on httpWebRequest object when debug to command line httpwebRequest.GetResponseAsync() inside sendhttpRequest method of RESTApi class. I checked httpwebRequest object which created by both case (winform app and web MVC app). They are not difference for same API. I think this error relate to the differences between multi-thread mechanism of winform app and Web MVC app. Can you kindly suggest me some solution for this problem, Mr. XiaoFaye ! I am very grateful for that !

XiaoFaye commented 7 years ago

can you post some of your code so I can have a look?

trungdh commented 7 years ago

Yes, sure Mr XiaoFaye I've wrote some php code in class-wc-api-customers.php file of Woocommerce to make more an API which allow authenticate user login from third party system. Here is php code which I added into class-wc-api-customers.php file : 1. Register router: # GET /customers/login/<usernamepass> $routes[ $this->base . '/login/(?P<usernamepass>.+)' ] = array( array( array( $this, 'login_customer' ), WC_API_SERVER::READABLE ), ); 2. login_customer function : `public function login_customer($usernamepass) {

            list($username, $password) = split('[-]', $usernamepass);   

    // Checks the username.
    if ( ! isset( $username) ) {
        return new WP_Error( 'woocommerce_api_missing_customer_username', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'username' ), array( 'status' => 400 ) );
    }

    // Checks the password.
    if ( ! isset( $password) ) {
        return new WP_Error( 'woocommerce_api_missing_customer_password', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'password' ), array( 'status' => 400 ) );
    }

    // Attempts to login customer
    $credentials = array();
    $credentials['user_login'] = $username;
    $credentials['user_password'] = $password;
    $credentials['remember'] = true;
    $user = wp_signon( $credentials, false );

    // Checks for an error in the customer login.
    if ( is_wp_error( $user) ) {
        return new WP_Error( 'woocommerce_api_cannot_login_customer', $user->get_error_message(), array( 'status' => 400 ) );
    }

    do_action( 'woocommerce_api_login_customer', $user );

    $this->server->send_status( 201 );

    return $this->get_customer( $user->ID );
}`

And I wrote a more method LoginCustomers inside WCObject class in your library: ` public async Task LoginCustomers(string username, string password, Dictionary<string, string> parms = null) {

        string json =  await API.SendHttpClientRequest("customers/login/" + username +"-"+password, RequestMethod.GET, string.Empty, parms);
        return json;           
    }`

Because the SendHttpClientRequest method got a Execption : object reference not be set to an instance of an object at command line : Stream dataStream = await httpWebRequest.GetRequestStreamAsync(); So, I changed code of that method became:

`public async Task SendHttpClientRequest(string endpoint, RequestMethod method, T requestBody, Dictionary<string, string> parms = null) {

        HttpWebRequest httpWebRequest = null; 
        try 
        { 
            string requestUrl = GetOAuthEndPoint(method.ToString(), endpoint, parms); 
            string responseString = ""; 
            using (var client = new HttpClient()) 
            { 
                client.BaseAddress = new Uri(wc_url); 
                client.DefaultRequestHeaders.Accept.Clear(); 
                client.DefaultRequestHeaders.Accept.Add(new       MediaTypeWithQualityHeaderValue("application/json")); 
                var response = client.GetAsync(requestUrl).Result; 
                responseString = response.ReasonPhrase; 
                if (response.IsSuccessStatusCode) 
                { 
                     responseString = response.Content.ReadAsStringAsync().Result; 
                } 
            } 
            return responseString; 
        } 
        catch (WebException we) 
        { 
            if (httpWebRequest != null && httpWebRequest.HaveResponse) 
                if (we.Response != null) 
                    throw new Exception(await GetStreamContent(we.Response.GetResponseStream(), we.Response.ContentType.Split('=')[1])); 
                else 
                    throw we; 
            else 
                throw we; 
        } 
        catch (Exception e) 
        { 
            return e.Message; 
        } 
    }` 

Here is code I used to call API from my MVC web api application :

`public async Task getWoocommerceCustomer(string uEmail, string uPassword)

    {
        try
        {
            RestAPI rest = new RestAPI("http://mycavnus.com/wc-api/v3/", "ck_xxx", "cs_xxx");
            WooCommerceNET.WooCommerce.Legacy.WCObject wc = new WooCommerceNET.WooCommerce.Legacy.WCObject(rest);
            string s = await wc.LoginCustomers(uEmail, uPassword);
            LogWriter.WriteLog("getWoocommerceCustomer s = " + s);
            return s;
        }
        catch (NullReferenceException e)
        {
            String ex = e.ToString();
            return null;
        }
    }`

Now, it was worked, I can call my check_login API which I wrote on Woocommerce system from MVC Web api application. But when I tested on local, it worked as well. I got json object as below for case enter correct account and password : { "customer":{ "id":3, "created_at":"2016-08-30T18:55:27Z", "last_update":"2016-11-26T13:22:00Z", "email":"customerregistered@gmail.com", "first_name":"Acid", "last_name":"Burn", "username":"acidburn", "role":"administrator", "last_order_id":null, "last_order_date":null, "orders_count":0, "total_spent":"0.00", "avatar_url":"http:\/\/0.gravatar.com\/avatar\/?s=96", "billing_address":{ "first_name":"", "last_name":"", "company":"", "address_1":"", "address_2":"", "city":"", "state":"", "postcode":"", "country":"", "email":"", "phone":"" }, "shipping_address":{ "first_name":"", "last_name":"", "company":"", "address_1":"", "address_2":"", "city":"", "state":"", "postcode":"", "country":"" } } } But when I deploy my code to server (VPS - I used Window server 2012 R2). API not work correctly. It always return Unauthorized even when I used correct account and password. I don't know why it worked correctly on local but not work on Server. Can you please kindly help me, Mr XiaoFaye. I am very grateful for that !

trungdh commented 7 years ago

ah, Additional, our server already installed .NET framework 4.5, Mr XiaoFaye

XiaoFaye commented 7 years ago

are your WooCommerce website and the MVC Web api application on the same server?

trungdh commented 7 years ago

No, they be published on difference server. Will that impact to communication between them ? And when I tested MVC Web api app on local, only it run on my computer, I still call to Woocommerce's API which be deployed on other IP address. It work correctly. But run it on server. It don't work like that

XiaoFaye commented 7 years ago

I just have a feeling that if they are in different timezones, which might cause problems.

Another reason might be the following:

Go to the source code line 61, 62 of RestAPI.cs, you should see above codes:

//httpWebRequest.Credentials = new NetworkCredential(wc_key, wc_secret);
httpWebRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(wc_key + ":" + wc_secret));

Can you please try comment the second line and uncomment the first line?

Some servers might have problem to send the Authorization info in the header. I had fixed this issue on my local but haven't got time to push the changes to Github.

trungdh commented 7 years ago

Hi Mr XiaoFaye I've config IIS and deployed a server version on my local computer. Then I tested with that API, it's work correctly. So, I think the reason it not work on my VPS is not different timezones. Because the timezones of my local and server where stored for Woocommerce system is different About the second reason : Because our website used http protocol. So, I think it will not go to command line //httpWebRequest.Credentials = new NetworkCredential(wc_key, wc_secret); or httpWebRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(wc_key + ":" + wc_secret)); Two command line inside command block : if (wc_url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) { //httpWebRequest.Credentials = new NetworkCredential(wc_key, wc_secret); httpWebRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(wc_key + ":" + wc_secret)); } I used IIS and deployed on local (not debug mode), it work fine. I don't know why it not work on my server, Mr XiaoFaye :(. Do you have any idea for my problems ?

XiaoFaye commented 7 years ago

I can't think of anything else now, but I will suggest you to do a API testing using Postman/Curl, at least we will know the problem is in the wrapper or the server.

trungdh commented 7 years ago

I also used Postman tool for test API. when I used my local server address for Postman tool and call that API ( include both debug mode and local server deployed cases). they got same result. they received user object with json format which be response from Woocommerce system. But when I deploy that code to VPS server then used VPS server address for Postman tool and call it. It always got result : Unauthorized

XiaoFaye commented 7 years ago

this could be a server configuration issue, try to intercept network packages and see what happen.

trungdh commented 7 years ago

Hi Mr XiaoFaye, I used Postman tool and call my API by a request to my Woocommerce website with both account and password are valid. It's look like (I get oauth_nonce, oauth_signature...from debug mode, but I not get yet response to make sure oauth_nonce can be used for Postman) :

http://mydomain.com/wc-api/v3/customers/login/customerregistered@gmail.com-abc12@?oauth_consumer_key=ck_2464997a25cb28f0965d0972e5b86712f992f405&oauth_nonce=d8ecfdf6f0c1a03c14f81ea7b78927dac62703d9&oauth_signature_method=HMAC-SHA256&oauth_timestamp=1480520670&oauth_signature=V43a%2BZBnr6FyhW9rgAR3yiJANuIw8hL8YQtcoCqK6g4%3D

And I got response from Postman tool :

{ "customer": { "id": 3, "created_at": "2016-08-30T18:55:27Z", "last_update": "2016-11-30T15:44:58Z", "email": "customerregistered@gmail.com", "first_name": "Acid", "last_name": "Burn", "username": "AcidBurn", "role": "administrator", "last_order_id": null, "last_order_date": null, "orders_count": 0, "total_spent": "0.00", "avatar_url": "http://2.gravatar.com/avatar/?s=96", "billing_address": { "first_name": "", "last_name": "", "company": "", "address_1": "", "address_2": "", "city": "", "state": "", "postcode": "", "country": "", "email": "", "phone": "" }, "shipping_address": { "first_name": "", "last_name": "", "company": "", "address_1": "", "address_2": "", "city": "", "state": "", "postcode": "", "country": "" } } }

But when I call API by a request from my server - It's same format with request which I used for Postman. I got response : (I wrote response into a log file)

{ "Version":{ "Major":1, "Minor":1, "Build":-1, "Revision":-1, "MajorRevision":-1, "MinorRevision":-1 }, "Content":{ "Headers":[ { "Key":"Content-Type", "Value":[ "application/json; charset=UTF-8" ] } ] }, "StatusCode":401, "ReasonPhrase":"Unauthorized", "Headers":[ { "Key":"Keep-Alive", "Value":[ "timeout=2, max=100" ] }, { "Key":"Connection", "Value":[ "Keep-Alive" ] }, { "Key":"Transfer-Encoding", "Value":[ "chunked" ] }, { "Key":"Date", "Value":[ "Wed, 30 Nov 2016 15:34:35 GMT" ] }, { "Key":"Server", "Value":[ "Apache" ] }, { "Key":"X-Powered-By", "Value":[ "PHP/5.6.25" ] } ], "RequestMessage":{ "Version":{ "Major":1, "Minor":1, "Build":-1, "Revision":-1, "MajorRevision":-1, "MinorRevision":-1 }, "Content":null, "Method":{ "Method":"GET" }, "RequestUri":"http://mydomain.com/wc-api/v3/customers/login/customerregistered@gmail.com-abc12@?oauth_consumer_key=ck_2464997a25cb28f0965d0972e5b86712f992f405&oauth_nonce=5fc67b0065d0adf807ac69292723b198ec2b98d4&oauth_signature_method=HMAC-SHA256&oauth_timestamp=1480548873&oauth_signature=yVXcpu43yJWvkpjpHYzAVfhAWFDjFvzkC7Bm%2FX%2F62DE%3D", "Headers":[ { "Key":"Accept", "Value":[ "application/json" ] } ], "Properties":{ } }, "IsSuccessStatusCode":false }

With this result, do you have any idea for my problems, Mr XiaoFaye. Really many thanks for your help !!!!

trungdh commented 7 years ago

Hi Mr XiaoFaye, I tried to modified code of Woocommerce inside v3/class-wc-api-authentication.php file to find the cause of 401 error which I got. And I found the cause of my problem is Invalid timestamp. I think maybe you right, the reason because different between timezones of my server and Woocommerce server. Woocommerce server has timezones GTM-5 and my server has timezones GTM +7 :( . Has any way to synchronized timezones for both server, Mr XiaoFaye ? Or do I have to move a server to the same place with the remaining server :)

XiaoFaye commented 7 years ago

Could you please have a look at the below link? https://github.com/woocommerce/woocommerce/issues/8645

The GetUnixTime method in the dll is using UTC time, according to the link above, it should be all good. Can you confirm that your server time is correct?

Maybe we can still force to sync timezones for both server, but that doesn't sound like the best solution.

trungdh commented 7 years ago

Many thanks, Mr XiaoFaye. I've fixed my problem about difference timezone between two my server by changed the timezone of Service Server system (Web MVC API applicaiton) :), And now, it work as well, Mr XiaoFaye :). I can get customer's data through API which I wrote on Woo system. So, one more time, thank you so much for help. I am very grateful for that !

XiaoFaye commented 7 years ago

Good to hear that :)