top of page

QuickBooks Online x Perigee: A Dynamic Duo ๐Ÿค

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

11 views0 comments

Recent Posts

See All
bottom of page