Background
To access QBO via automation, developers can leverage QBO 3's mixed authentication. This allows the developer's software to access QBO functionality without the need to explicitly call Security/Login.ashx. The mixed authentication works as follows:
If there is no User-Agent HTTP header, basic authentication will be used
If there is an HTTP header matching any one of the headers specified in qbo.Security.Properties.Settings.BasicAuthenticationHeaders, basic authentication will be used
If the HTTP User-Agent header contains any of the user agents strings specified in qbo.Security.Properties.Settings.BasicAuthenticationUserAgents, basic authentication will be used
Otherwise, forms authentication will be used
By default, the configuration settings are set to:
BasicAuthenticationHeaders: SOAPAction
BasicAuthenticationUserAgents: Gecko/ /21.0
These may be extended via the UI from Design > Configuration > Modules > Person > Settings. Note that multiple headers and user agents may be specified with a semi-colon delimited list:
BasicAuthenticationHeaders: SOAPAction;MyCustomHeader
BasicAuthenticationUserAgents: Gecko/ /21.0;curl
Sample Code
See the attached sample C# project. The key code is below.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace qbo.Sample
{
class Program
{
static void Main(string[] args)
{
// Prompt the user for the {Folder}/{ClassName}/{Operation}?{Parameters} to invoke
Console.Write("Enter Module (e.g. Application or Decision): ");
string module = Console.ReadLine();
Console.Write("Enter Class (e.g. Matrix, SmartWorklist, Attachment): ");
string className = Console.ReadLine();
Console.Write("Enter Method (e.g. Summary, Generate, Lookup): ");
string method = Console.ReadLine();
Console.Write("Parameters (e.g. ID=1&OtherParam=ABC): ");
string parameters = Console.ReadLine();
// Construct the URL to call; the root website URL is stored in app.config
string url = string.Format("{0}{1}/{2}.ashx/{3}?{4}&Output={5}", Properties.Settings.Default.BaseUrl, module, className, method, parameters, Properties.Settings.Default.Output);
// Figure out where to store the response we get back; OutputPath is defined in app.config
string outputFile = string.Format("{0}/{1}.{2}", Properties.Settings.Default.OutputPath, Guid.NewGuid(), Properties.Settings.Default.Output);
WebRequest request = WebRequest.Create(url);
// Establish the user name and password; since we're using a WebRequest, there will be no user-agent header, so QBO 3 will issue a Basic Auth challenge (HTTP 401)
CredentialCache cache = new CredentialCache();
// The sample username and password are stored in app.config
cache.Add(new Uri(Properties.Settings.Default.BaseUrl), "Basic", new NetworkCredential(Properties.Settings.Default.Username, Properties.Settings.Default.Password));
request.Credentials = cache;
request.PreAuthenticate = true;
// Make the call
using (WebResponse response = request.GetResponse())
{
using (Stream from = response.GetResponseStream())
{
int BUFFER_SIZE = 4096;
Byte[] buffer = new Byte[BUFFER_SIZE];
int bytesRead = from.Read(buffer, 0, BUFFER_SIZE);
using (FileStream file = new FileStream(outputFile, FileMode.OpenOrCreate))
{
while (bytesRead > 0)
{
file.Write(buffer, 0, bytesRead);
// Console.Write(Encoding.Default.GetString(buffer));
bytesRead = from.Read(buffer, 0, BUFFER_SIZE);
}
}
}
}
// Let the user know what we did
Console.WriteLine(string.Format("\nURL: {0}", url));
Console.WriteLine(string.Format("Output file: {0}", outputFile));
Console.WriteLine("\n\nPress enter to exit");
Console.ReadLine();
}
}
}
Using 'X-ApiKey' Headers
An alternative to using Basic Authentication is to issue a SecurityAccess row mapping to a desired user account, and have the client issue a custom HTTP header:
X-ApiKey: {SecurityAccess.GUID}
Sample test code can be found in qbo.Security.Tests project in the qbo.Core.Tests solution:
[TestMethod]
public void LoginWithApiKey()
{
// Create a SecurityAccess row mapped to the TestUser account (defaults to admin@quandis.com).
SecurityAccessObject access = new SecurityAccessObject(User)
{
SecurityAccess = "ApiKey Test",
GUID = Guid.NewGuid(),
PersonID = User.UserID
};
access.Save();
// Perform a PersonSearch.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("{0}/Security/Person.ashx/Search?DisplaySize=5", Properties.Settings.Default.TestWebsite));
request.Headers.Add("X-ApiKey", access.GUID.ToString());
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "The response code is OK (200)");
Assert.AreNotEqual(HttpStatusCode.Redirect, response.StatusCode, "The response code is not Redirect (301)");
Assert.IsNotNull(response.Headers["Set-Cookie"], "A Set-Cookie header exists");
Assert.IsTrue(response.Headers["Set-Cookie"].Contains("ASP.NET_SessionId"), "The Set-Cookie header includes an ASP.NET_SessionId cookie.");
Assert.IsTrue(response.Headers["Set-Cookie"].Contains("qbo.Security"), "The Set-Cookie header includes qbo.Security cookie.");
}
}