End If Software

...for the answers to your 'what if' questions

  • About Us
  • Qualifications
  • Contact
  • Support
  • Small Biz
  • Get a Quote

Finally, the end to QBO Authorization Nightmare

05.18.2023 by stephanie //

I’ve been struggling with oAuth2 and the QBO API authorization for several weeks now, and I am so frustrated with the lack of documentation and end-to-endexamples of how to get the tokens based on the client access information, the expiration of the refresh token, and the expiration of the access token.

I’ve finally gotten through each detail of the steps required! Using the Postman tools, which does a great job of obtaining and refreshing the codes, I’ve studied the logs to get the correct syntax. You can get the full details of the request submission, including the headers and parameters, in the Postman log viewer. The only remaining challenge is to understand how to determine if you need just to use the existing refresh tokend, refresh the token, or obtain a new access code. It is critical that these codes and expirations are stored in a database or in a log file (not recommended).

When connecting, here is the flow:

  • Find the last values received. If none exist, get a new access token.
  • If the token exists, check if the Access token is expired. The Access token has a lifetime of 100 days, so store the expiration date. If expired, get a new Access token.
  • If the token exists, check if the Refresh token is expired. The Refresh token has a lifetime of 3600 seconds. Also store this expiration date/time. If expired, get a new Refresh token.
  • If the token exists and nothing is expired, use the last Access token.

Understanding this flow is key to success. A request can be submitted, but if it is the wrong type for the expiration information which QBO previously returned, you will receive a frustrating 401 error, and a meaningless error message which tells you nothing.

Here is my code, short and sweet. Step 1: set up the values to send to the Authorization Server, and determine the type of request to send.

public static string GetNewQBKey()
        {
            // grant types:  "refresh_token", "authorization_code"
            string grant_type = "";
            string ClientId = Utilities.Utility.getsettingName("QBClientId");
            string ClientSecret = Utilities.Utility.getsettingName("QBClientSecret");
            string AuthURL = Utilities.Utility.getsettingName("QBOAuthURL");
            string OldRefreshToken = tbl_Parameters.FetchStringParamValue("QBRefresh_token");
            string OldAccessToken = tbl_Parameters.FetchStringParamValue("QBAccess_token");
            string PortalReturnUrl = Utility.getsettingName("PortalReturnUrl");
            string QBScope = Utility.getsettingName("QBscope");
            DateTime AccessExpires = Convert.ToDateTime(tbl_Parameters.FetchStringParamValue("QBAccessTokenExpires"));
            DateTime RefreshExpires = Convert.ToDateTime(tbl_Parameters.FetchStringParamValue("QBRefreshTokenExpires"));
            var todaysDate = DateTime.Now;
            var token = "";
            //if the  Access Token Expiration is before NOW, then get a new authorization code
            if (todaysDate > AccessExpires)     // Access token has expired (100 days since last issuance)
            {
                //Get New Authorization Access token
                grant_type = "authorization_code";  //IMPORTANT
                token = AccessCode(AuthURL, grant_type, ClientId, ClientSecret, OldRefreshToken, PortalReturnUrl, QBScope);
            }
            else
            {
                if (todaysDate > RefreshExpires)    //Refresh token has expired, need to refresh it
                {
                    //Refresh Token
                    grant_type = "refresh_token"; //IMPORTANT
                    token = RefreshCode(AuthURL, grant_type, ClientId, ClientSecret, OldRefreshToken, PortalReturnUrl, QBScope);
                }
                else
                {
                    //Stored Refresh token is still active, just send back the current refresh token
                    token = OldAccessToken;
                }
            }

            if (token.StartsWith("ERROR"))
            {
                Exception oEx = new Exception();
                Utility.WriteToEventLog(oEx, "QuickbooksUtils - GetNewQBKey " + token);
            }
            return token;
        }

Fetch the new Refresh token & update the database:

public static string RefreshCode(string AuthURL, string grant_type, string ClientId, string ClientSecret, string OldRefreshToken, string PortalReturnUrl, string scope)
        {
            string token = "";
            try
            {
                var client = new RestClient(AuthURL);
                var request = new RestRequest(Method.POST);
                request.AddHeader("Connection", "keep-alive");
                request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
                request.AddHeader("Cache-Control", "no-cache");
                request.AddHeader("Host", "oauth.platform.intuit.com");
                request.AddHeader("Accept-Encoding", "gzip, deflate, br");
                request.AddHeader("Accept", "*/*");

                request.AddParameter("grant_type", grant_type);
                request.AddParameter("client_id", ClientId);
                request.AddParameter("client_secret", ClientSecret);
                request.AddParameter("refresh_token", OldRefreshToken);
                request.AddParameter("redirect_uri", PortalReturnUrl);
                request.AddParameter("response_type", "code");
                request.AddParameter("scope", scope);
                request.AddParameter("state", "ma****cu*****er***al");
                IRestResponse response = client.Execute(request);

                if (!response.IsSuccessful)
                {
                    token = "ERROR: " + response.ErrorException + "; Message: " + response.ErrorMessage;
                }
                else
                {
                    var result = JsonConvert.DeserializeObject<dynamic>(response.Content);
                    token = result.access_token;
                    tbl_Parameters.UpdateParameterValue((String)token, "QBAccess_token", General.SQLDBHandling.getSQLConnectionString());
                    var refresh_token = result.refresh_token;
                    tbl_Parameters.UpdateParameterValue((String)refresh_token, "QBrefresh_token", General.SQLDBHandling.getSQLConnectionString());
                    var refreshtokenexpires = result.expires_in;
                    DateTime refreshexpires = DateTime.Now.AddSeconds(Convert.ToInt32((string)refreshtokenexpires));
                    tbl_Parameters.UpdateParameterValue(refreshexpires.ToString(), "QBRefreshTokenExpires", General.SQLDBHandling.getSQLConnectionString());

                    var accesstokenexpires = result.x_refresh_token_expires_in;
                    DateTime accessexpires = DateTime.Now.AddSeconds(Convert.ToInt32((string)accesstokenexpires));
                    tbl_Parameters.UpdateParameterValue(accessexpires.ToString(), "QBAccessTokenExpires", General.SQLDBHandling.getSQLConnectionString());
                }
                return token;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

If a new Access token is required, this is the method to retrieve the new token:

public static string AccessCode(string AuthURL, string grant_type, string ClientId, string ClientSecret, string OldRefreshToken, string PortalReturnUrl, string scope)
        {
            string token = "";
            try
            {
                var client = new RestClient(AuthURL);
                var request = new RestRequest(Method.POST);
                request.AddHeader("Connection", "keep-alive");
                request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
                request.AddHeader("Cache-Control", "no-cache");
                request.AddHeader("Host", "oauth.platform.intuit.com");
                request.AddHeader("Accept-Encoding", "gzip, deflate, br");
                request.AddHeader("Accept", "*/*");

                request.AddParameter("grant_type", grant_type);
                request.AddParameter("client_id", ClientId);
                request.AddParameter("client_secret", ClientSecret);
                request.AddParameter("refresh_token", OldRefreshToken);
                request.AddParameter("redirect_uri", PortalReturnUrl);
                request.AddParameter("response_type", "code");
                request.AddParameter("scope", scope);
                request.AddParameter("state", "ma****cu*****er***al");
                IRestResponse response = client.Execute(request);

                if (!response.IsSuccessful)
                {
                    token = "ERROR: " + response.ErrorException + "; Message: " + response.ErrorMessage;
                }
                else
                {
                    var result = JsonConvert.DeserializeObject<dynamic>(response.Content);
                    token = result.access_token;
                    tbl_Parameters.UpdateParameterValue((String)token, "QBAccess_token", General.SQLDBHandling.getSQLConnectionString());
                    var refresh_token = result.refresh_token;
                    tbl_Parameters.UpdateParameterValue((String)refresh_token, "QBrefresh_token", General.SQLDBHandling.getSQLConnectionString());
                    var refreshtokenexpires = result.expires_in;
                    DateTime refreshexpires = DateTime.Now.AddSeconds(Convert.ToInt32((string)refreshtokenexpires));
                    tbl_Parameters.UpdateParameterValue(refreshexpires.ToString(), "QBRefreshTokenExpires", General.SQLDBHandling.getSQLConnectionString());

                    var accesstokenexpires = result.x_refresh_token_expires_in;
                    DateTime accessexpires = DateTime.Now.AddSeconds(Convert.ToInt32((string)accesstokenexpires));
                    tbl_Parameters.UpdateParameterValue(accessexpires.ToString(), "QBAccessTokenExpires", General.SQLDBHandling.getSQLConnectionString());
                }
                return token;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

“Using” statements for this code:

using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RestSharp;
using System.Data;
using Ma*******al.Utilities;

So that’s it. Hope this helps someone else get through the process!

Categories // End If Services

Quick Links

  • About Us
  • Request a Quote
  • Small Biz Shopping List
  • Small Business Services

Recommended

Check out MXGuardDog, our recommended spam protect service!
Recommended article about how to block spam emails at MXGuardDog!

Recent Posts

  • Finally, the end to QBO Authorization Nightmare
  • New Client Activity – Great Spirits Baking Company
  • New Client Activity – Silicon Valley Research Group
  • OutSystems Development and Consulting
  • Small Business Services

Copyright © 2025 · Modern Studio Pro on Genesis Framework · WordPress · Log in

Loading...

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.