top of page

QuickBooks Online x Perigee: A Dynamic Duo šŸ¤

  • Writer: Zach - Product Owner
    Zach - Product Owner
  • Aug 27, 2023
  • 3 min read

QuickBooks Online - Introduction


Working with OAuth Flow can be a double-edged sword šŸ—”ļø. While it's crucial for securely accessing APIs, managing tokens, authorization state, and timeouts can be challenging. Get it wrong, and you may find yourself locked out of a resource or stuck with an expired token during an important operation. QuickBooks Online (QBO) offers a comprehensive guide and an even better playground for developers šŸ› ļø, especially those just starting with OAuth. But, here comes the tricky part: you have to manage independent sets of authorization and refresh tokens for each authorized company (known as a realm) within your application. Enter Perigee šŸš€. This article breaks down how to use Perigee to effortlessly manage these tokens across multiple realms.

The Core of the Process 🧠

The following C# code snippet is executed immediately after receiving the `RealmID` and `Code` parameters from the API callback:


//Create a client pointing to the QBO Token Endpoint
var authClient = new RestClient(DiscoveryDocument.TokenEndpoint);
var authReq = new RestRequest("", Method.Post);

//Authorize the code request
authClient.UseAuthenticator(new RestSharp.Authenticators.HttpBasicAuthenticator(
    iconfig.GetValue<string>("AppSettings:client_id")!, 
    iconfig.GetValue<string>("AppSettings:client_secret")!));

//Add parameters
authReq.AddParameter("redirect_uri", iconfig.GetValue<string>("AppSettings:redirect"));
authReq.AddParameter("grant_type", "authorization_code");
authReq.AddParameter("code", code);

//Execute - Getting back an initial authorization code and refresh code. 
var rsp = authClient.ExecuteRetry<QBAuth.Tokens>(authReq, 2);

if (rsp.IsSuccessful)
{
    //Each credential is assigned a name: QBA- + realmID
    string qbaName = $"QBA-{realmId}";

    //Step 1) Persist an initial credential we received from the token callback
    CredentialStore.PersistCredential(
    new RestSharpCredentialStoreItem(new RestSharp.Authenticators.JwtAuthenticator(rsp.Data?.AccessToken ?? ""), DateTimeOffset.UtcNow.AddSeconds(rsp.Data?.ExpiresIn ?? 3000))
        {
            RefreshToken = rsp.Data?.RefreshToken,
            StoreA = rsp.Data?.IdToken,
            Environment = realmId,
            Name = qbaName
        });
        
    //Step 2) Register realm refresh for future usage on expiration
    CredentialStore.RegisterRefresh(qbaName, (o) =>
    {
        var authClient = new RestClient(DiscoveryDocument.TokenEndpoint.ToString());
        var authReq = new RestRequest("", Method.Post);

        authClient.UseAuthenticator(new RestSharp.Authenticators.HttpBasicAuthenticator(iconfig.GetValue<string>("AppSettings:client_id")!, iconfig.GetValue<string>("AppSettings:client_secret")!));
        authReq.AddParameter("redirect_uri", iconfig.GetValue<string>("AppSettings:redirect")!);
        authReq.AddParameter("grant_type", "refresh_token");
        authReq.AddParameter("refresh_token", CredentialStore.GetRefreshToken(qbaName));
        var rsp = authClient.ExecuteRetry<QBAuth.Tokens>(authReq, retries: 1);

        if (rsp.IsSuccessful)
        {
            //Get previous expired credential using peek to pull environment from last run...
            var expCred = CredentialStore.PeekCredential(qbaName);

            return new RestSharpCredentialStoreItem(new RestSharp.Authenticators.JwtAuthenticator(rsp.Data?.AccessToken ?? ""), DateTimeOffset.UtcNow.AddSeconds(rsp.Data?.ExpiresIn ?? 3000))
                {
                    RefreshToken = rsp.Data?.RefreshToken,
                    StoreA = rsp.Data?.IdToken,
                    Environment = expCred?.Environment ?? "",
                };
        }
        else
        {
            return new FaultedCredentialStoreItem($"Couldn't refresh token for realm {qbaName}, {rsp.Content}");
        }
    });
}

Re-Register the Callback on Application Startup šŸ”„

Since each realm has its own set of authorization details, it's important to re-register the callback functions for each realm when the application restarts:


foreach (var cred in CredentialStore.GetCredentialsByPredicate(f => f.Name.StartsWith("QBA-")))
{
    var QBAName = cred.Name;
    
    //TODO: Call the register function again listed above. It would be wise to encapsulate that mathod to use in multiple places!
}

Making Authorized Realm Calls: Simpler Than Ever 🄳

Thanks to our prior setup with Perigee, making authorized realm calls is now a walk in the park 🌳. The system ensures that you get a freshly refreshed authorization code each time you hit an endpoint. Plus, it authorizes the call pre-emptively, reducing the risk of a failure midway through a process.


var realmID = 123456789;

//New client (this is using sandbox, you *should* be querying the discovery endpoint for this)
client = new RestClient(new RestClientOptions("https://sandbox-quickbooks.api.intuit.com"));

//Use the credential authenticator here, with the QBA-Realm key! Voila!!!
client.UseAuthenticator(new CredentialAuthenticator($"QBA-{realmID}"));

var rsp = client.ExecuteRetry<CompanyInfo.QueryResponse>($"/v3/company/{realmID}/query?query=select * from CompanyInfo", retries: RetryCount);

In conclusion, while OAuth can often feel like a labyrinth of tokens and authorizations, Perigee simplifies the process, making it far easier and more reliable. Happy coding from the Perigee Team! šŸŽ‰




Email Icon

Follow all the exciting developments of the Perigee Revolution by joining our Perigee Email List today! Or simply email us at info@perigee.software



Youtube icon

Watch the latest disrupting videos at Perigee Software - YouTube



twitter icon

Tweet at us at @PerigeeNinja




documentation icon

Get full documentation with ready-to-run examples at Perigee Documentation




Facebook icon

Like us on Facebook at Perigee Facebook Page

Ā 
Ā 
Ā 

Recent Posts

See All

Comments


bottom of page