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!