OAuth 2.0 for server-side web apps

In order for an app to access data in a QuickBooks Online company, it must implement the OAuth 2.0 protocol for authorization. This document explains how web server apps use Intuit OAuth 2.0 endpoints to implement the OAuth 2.0 authorization workflow. For an interactive demonstration using the Intuit OAuth 2.0 endpoints, experiment with the OAuth 2.0 playground.

These sections take you through the workflow in detail:

All apps must implement this workflow to go live. For details about using OAuth 2.0 to enable your app on the QuickBooks app store, click here

Note

As of July 17, 2017, new or existing developers with no previous apps must implement OAuth 2.0. Click here for OAuth 1.0a documentation, available as a reference for existing applications. 

Note
  • Only the Master Administrator or a Company Administrator can authorize access to a QuickBooks Online company.

Prerequisites

  • Create an Intuit Developer account and app: You must have an Intuit Developer account and have created an app within that account. The app serves as a container for the OAuth workflow.
  • Get client keys: Obtain OAuth 2.0 client keys from your app's dashboard on developer.intuit.com.  To locate the app's dashboard, sign in to developer.intuit.com and click My Apps. Find and open the app you want. From here, click the Keys tab. There are two versions of this key:
    • Development keys—use only in the sandbox environment.
    • Production keys—use only in the production environment. 
  • Define redirect URIs: On the app setting page, create one or more redirect URIs. These URIs handle responses from the OAuth 2.0 server and are called after the user authorizes the connection. URIs in this list are the only ones to which the authorization response can be sent from the OAuth 2.0 server.   You must define at least one URI specifically for your application's auth endpoint before you can use OAuth 2.0. For the sandbox environment, this list can include http://localhost. As a best practice, design your app's auth endpoints in a way that doesn't expose authorization codes to other resources on the page.

Sample code

Initiating the authorization request

Making the request

Initiate the OAuth 2 process by redirecting the user to Intuit's OAuth 2.0 server. Retrieve the base URI from the discovery document using the key, authorization_endpoint. The discussion here assumes the base URI is: 

 GET https://appcenter.intuit.com/connect/oauth2

This endpoint is accessible over https; plain http connections are refused. Ensure query parameter values are always URL encoded. The set of query  parameters supported by the Intuit OAuth server include:

ParameterValuesDescription
client_idThe client ID you obtain from the developer dashboard.

Required. Identifies which app is making the request. Obtain this value from the Keys tab on the app profile via My Apps on the developer site. There are two versions of this key:

  • Development key—use only in the sandbox environment.
  • Production key—use only in the production environment. 
scopeSpace-delimited set of permissions that the application requests.

Required. Identifies the QuickBooks Online API access that your application is requesting. The values passed in this parameter inform the consent screen that is shown to the user.  Available scopes include:

  • com.intuit.quickbooks.accounting—QuickBooks Online API

  • com.intuit.quickbooks.payment—QuickBooks Payments API

Click here for OpenID Connect scopes.

It is generally a best practice to request scopes incrementally, at the time access is required, rather than up front; see Incremental authorization.

redirect_uriOne of the redirect URI values listed for this project in the developer dashboard.Required. Determines where the response is sent. The value of this parameter must exactly match one of the values listed for this app in the app settings. This includes the https scheme, the same case, and the trailing '/'. For the sandbox environment, this list can include http://localhost (no HTTPS with localhost). IP addresses are allowed for redirect URIs.
response_typecodeRequired. Determines whether the Intuit OAuth 2.0 endpoint returns an authorization code. Always set this to code.
stateAny string

Required. Provides any state that might be useful to your application upon receipt of the response. The Intuit Authorization Server roundtrips this parameter, so your application receives the same value it sent. To mitigate against cross-site request forgery (CSRF), it is strongly recommended to include an anti-forgery token in the state, and confirm it in the response. See About anti-forgery state tokens.

Here is an example of a complete authorization request URI specifying the com.intuit.quickbooks.accounting scope, with line breaks and spaces for readability:

GET https://appcenter.intuit.com/connect/oauth2?
 client_id=Q3ylJatCvnkYqVKLmkH1zWlNzNWB5CkYB36b5mws7HkKUEv9aI&
 response_type=code&
 scope=com.intuit.quickbooks.accounting&
 redirect_uri=https://www.mydemoapp.com/oauth-redirect&
 state=security_token%3D138r5719ru3e1%26url%3Dhttps://www.mydemoapp.com/oauth-redirect&

Handling the response

The response is sent to the redirect_uri that you specified in the request. All responses are returned in the query string, as shown below:

https://www.mydemoapp.com/oauth-redirect?state=security_token%3D138r5719ru3e1%26url
%3Dhttps://www.mydemoapp.com/oauth-redirect&code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&realmId=1231434565226279
  • Note the authorization code returned in the code query parameter. You use it later in the flow.
  • Note the realmId. You use it in subsequent API endpoint URLs when retrieving data from the QuickBooks Online company. Keep track of the realmID from each response and check it against previous responses.  A duplicate realmID means that a user's existing connection has transferred to another user. Make sure your app handles this situation as appropriate. For instance, you may want to make sure you save settings from the first user.
  • Confirm that the state received from Intuit matches the state token you sent in the authentication request. This round-trip verification helps to ensure that the user, not a malicious script, is making the request.

Error responses

Certain error conditions trigger the single query parameter, error=, to be sent to the redirect URI.

Error responseDescription
access_deniedThe user did not authorize the request.
invalid_scopeAn invalid scope string was sent in the request.
Note

If your response endpoint renders an HTML page, any resources on that page will be able to see the authorization code in the URL. Scripts can read the URL directly, and all resources may be sent the URL in the Referer HTTP header. Carefully consider if you want to send authorization credentials to all resources on that page (especially third-party scripts such as social plugins and analytics). To avoid this issue, we recommend that the server first handle the request, then redirect to another URL that doesn't include the response parameters.

Exchange code for refresh and access tokens

Making the request

After the app receives the authorization code, it exchanges the authorization code for refresh and access tokens. Retrieve the base URI from the discovery document using the key, token_endpoint. The discussion here assumes the base URI is:

POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer 

This endpoint is accessible over https; plain http connections are refused.

FieldDescription
codeRequired. The authorization code returned from the initial request.
redirect_uriRequired. One of the redirect URIs listed for this project in the developer dashboard.
grant_typeRequired. As defined in the OAuth 2.0 specification, this field must contain a value of authorization_code.

The actual request might look like the following:

POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer HTTP/1.1
Accept: application/json
Authorization: Basic UTM0dVBvRDIwanp2OUdxNXE1dmlMemppcTlwM1d2
    NzRUdDNReGkwZVNTTDhFRWwxb0g6VEh0WEJlR3dheEtZSlVNaFhzeGxma1l
    XaFg3ZlFlRzFtN2szTFRwbw==
Content-Type: application/x-www-form-urlencoded
Host: oauth.platform.intuit.com
Body: grant_type=authorization_code&
code=L3114709614564VSU8JSEiPkXx1xhV8D9mv4xbv6sZJycibMUI&
redirect_uri=https://www.mydemoapp.com/oauth-redirect
Generating the authorization header

Calculate the authorization header value as follows:

"Basic " + base64encode(client_id + ":" + client_secret)

where client_id and client_secret are located on the Keys the tab on the app profile via My Apps on the developer site. There are two versions of these keys:

  • Development keys—use only in the sandbox environment.
  • Production keys—use only in the production environment. 

Handling the response

A successful response to this request contains the following fields:

FieldDescription
access_tokenThe token that must be used to access the QuickBooks Online API.
refresh_tokenA token used when refreshing the access token.

x_refresh_token_
expires_in

The remaining lifetime, in seconds, for the connection, after which time the user must re-grant access. See refresh_token policy for details.
expires_inThe remaining lifetime of the access token in seconds. The value always returned is 3600 seconds (one hour). Use the refresh token to get a fresh one. See Refreshing the access token for further information.   
token_typeIdentifies the type of token returned. At this time, this field will always have the value Bearer.

 

A successful response is returned as a JSON array, similar to the following:

{ 
"token_type": "bearer", 
"expires_in": 3600, 
"refresh_token":"L311478109728uVoOkDSUCl4s8FDRvjHR6kUKz0RHe3WtZQuBq",
"x_refresh_token_expires_in":15552000,
"access_token":"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..KM1_Fezsm6BUSaqqfTedaA.
dBUCZWiVmjH8CdpXeh_pmaM3kJlJkLEqJlfmavwGQDThcf94fbj9nBZkjEPLvBcQznJnEmltCIvsTGX0ue_w45h7_
yn1zBoOb-1QIYVE0E5TI9z4tMUgQNeUkD1w-X8ECVraeOEecKaqSW32Oae0yfKhDFbwQZnptbPzIDaqiduiM_q
EFcbAzT-7-znVd09lE3BTpdMF9MYqWdI5wPqbP8okMI0l8aa-UVFDH9wtli80zhHb7GgI1eudqRQc0sS9zWWb
I-eRcIhjcIndNUowSFCrVcYG6_kIj3uRUmIV-KjJUeXdSV9kcTAWL9UGYoMnTPQemStBd2thevPUuvKrPdz3ED
ft-RVRLQYUJSJ1oA2Q213Uv4kFQJgNinYuG9co_qAE6A2YzVn6A8jCap6qGR6vWHFoLjM2TutVd6eOeYoL2bb7jl
QALEpYGj4E1h3y2xZITWvnmI0CEL_dYQX6B3QTO36TDaVl9WnTaCCgAcP6bt70rFlPYbCjOxLoI6qFm5pUwGLLp
67JZ36grc58k7NIyKJ8dLJUL_Q9r1WoUvw.ZS298t_u7dSlkfajxLfO9Q"
}
Note

Other fields may be included in the response, and your application should not treat this as an error. The set shown above is the minimum set.

Token storage best practices

In persistent storage, save the OAuth refresh_token and realmId, associating them with the user who is currently authorizing access.  Be sure to encrypt the refresh_token before saving it to persistent storage. Then, decrypt the it and store it in volatile memory when you need to use it to refresh the access_token.

The access_token is supplied in the authorization header for every call to a QuickBooks Online API resource. Store it in volatile memory so it is readily available during contexts in which calls to API resources are made. Examples of these contexts include:

  • The life of a signed-in user session.
  • The life of a connection.

Add the Connect to QuickBooks button

To enable the user to initiate this OAuth authorization workflow from you app, provide the Connect to QuickBooks button on one or more pages of your app using the artwork found here. Upon clicking this button, a popup window appears that begins the user authorization flow. During this flow, the user selects a company and then authorizes your app to access the data.

Refreshing the access token

Access tokens are valid for 3600 seconds (one hour), after which time you need to get a fresh one using the latest refresh_token returned to you from the previous request. 

Important

When you request a fresh access token, always use the refresh token returned in the most recent token_endpoint response. Your previous refresh tokens expire 24 hours after you receive a new one.

refresh_token policy

The refresh_token policy specifies the lifetime and expiry details of the refresh_token.

  • The time to request a new access_token is when a QuickBooks Online API call returns a 401 error.
  • The lifetime for the refresh_token returned with the initial access_token is set to 100 days.
  •  Always use the current refresh_token when requesting a new access_token. A new refresh_token is returned and the previous refresh_token is expired. This new refresh_token now has a lifetime of 100 days.
  • If an access_token is not requested during the current refresh_token 100 day lifetime, the refresh_token expires and access to the QuickBooks Online company terminates. 
  • As long as it hasn't expired, the refresh_token can be reset as often as needed within a one year access window from the time the original access_token was generated.
  • If the user revokes the connection to the QuickBooks Online company, the refresh_token is revoked and is no longer valid. 
  • Access to the QuickBooks Online company terminates when the current refresh_token expires or at the end of the one year access window from the time the original access_token was generated, whichever is earlier. When access terminates, your app must ask the user to reauthorize the connection. 
  • If your app uses an expired, revoked, or otherwise invalid refresh_token, it gets the following response:
{ 
    "error": "invalid_grant" 
}
IMPORTANT

When you request a fresh access token, always use the refresh token returned in the most recent token_endpoint response. Your previous refresh tokens expire 24 hours after you receive a new one.

Making the request

Retrieve the base URI for the request from the discovery document using the key, token_endpoint. The discussion here assumes the base URI is:

POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer 

This endpoint is accessible over https; plain http connections are refused.

FieldDescription
grant_typeRequired. When requesting a refresh token, set this to refresh_token.
refresh_tokenRequired. The refresh token returned in the last access token response.

The actual request might look like the following:

POST /oauth2/v1/tokens/bearer HTTP/1.1
Accept: application/json
Authorization: Basic UTM0dVBvRDIwanp2OUdxNXE1dmlMemppcTlwM1d2
    NzRUdDNReGkwZVNTTDhFRWwxb0g6VEh0WEJlR3dheEtZSlVNaFhzeGxma1l
    XaFg3ZlFlRzFtN2szTFRwbw==
Content-Type: application/x-www-form-urlencoded
Host: oauth.platform.intuit.com
Cache-Control: no-cache
Body: grant_type=refresh_token&
refresh_token=Q311488394272qbajGfLBwGmVsbF6VoNpUKaIO5oL49aXLVJUB

Handling the response

A successful response to this request contains the following fields:

FieldDescription
access_tokenThe token that must be used to access the QuickBooks Online API. The previous token is invalidated.
expires_inThe remaining lifetime of the access token in seconds. The value always returned is 3600 seconds (one hour). Use the refresh token to get a fresh one. See Refreshing the access token for further information.   
refresh_token

A token used to obtain a new access token. All previous refresh tokens are stale and unusable.

token_typeIdentifies the type of token returned. At this time, this field will always have the value Bearer.
x_refresh_token_
expires_in
The remaining lifetime, in seconds, for the connection, after which time the user must re-grant access. See refresh_token policy for details.

A successful response is returned as a JSON array, similar to the following:

{ 
"token_type": "bearer", 
"expires_in": 3600, 
"refresh_token":"Q311488394272qbajGfLBwGmVsbF6VoNpUKaIO5oL49aXLVJUB",
"x_refresh_token_expires_in":15551893,
"access_token":"eJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGGlyIn0..KM1_Fezsm6BUSaqqfTedaA.
dBUCZWiVmjH8CdpXeh_pmaM3kJlJkLEqJlfmavwGQDThcf94fbj9nBZkjEPLvBcQznJnEmltCIvsTGX0ue_w45h7_
yn1zBoOb-1QIYVE0E5TI9z4tMUgQNeUkD1w-X8ECVraeOEecKaqSW32Oae0yfKhDFbwQZnptbPzIDaqiduiM_q
EFcbAzT-7-znVd09lE3BTpdMF9MYqWdI5wPqbP8okMI0l8aa-UVFDH9wtli80zhHb7GgI1eudqRQc0sS9zWWb
I-eRcIhjcIndNUowSFCrVcYG6_kIj3uRUmIV-KjJUeXdSV9kcTAWL9UGYoMnTPQemStBd2thevPUuvKrPdz3ED
ft-RVRLQYUJSJ1oA2Q213Uv4kFQJgNinYuG9co_qAE6A2YzVn6A8jCap6qGR6vWHFoLjM2TutVd6eOeYoL2bb7jl
QALEpYGj4E1h3y2xZITWvnmI0CEL_dYQX6B3QTO36TDaVl9WnTaCCgAcP6bt70rFlPYbCjOxLoI6qFm5pUwGLLp
67JZ36grc58k7NIyKJ8dLJUL_Q9r1WoUvw.ZS298t_u7dSlkfajxLfO9Q"
}

Calling QuickBooks Online APIs

After your application obtains an access token, you can use it to make calls to QuickBooks Online API resources. To do this, include the access token in a request to the API by including it in the Authorization: Bearer HTTP header. You can try out all the QuickBooks Online APIs and view their scopes at the OAuth 2.0 Playground.

Example

A call to the production Invoice endpoint to read a given object using the access_token parameter might look like the following, though you'll need to specify your own access token:

GET https://quickbooks.api.intuit.com/v3/company/<companyID>/invoice/<invoiceID>
Authorization=Bearer eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..eF2J3XEDnGBjpO469rw9Dw.Gi3Oc
  UsfFm8Pnxw1aneLN-5o0j_nE0G9t6jbvAFgMy4-QYkmzUCVmgJWCj9HTZ2ojgQaKjYoEwZMO3uBvs
  QUo8UIdZIOQz_3HxXggq9VIlhihH7d1NXLiFMC8dQGtFhECGypv6xJ8Ob7Ay00CbEq_tns1wgfXXFgr
  L-FcoWtuubxHUjKE7EwlM8nB0VQG1VTwjWzEcPPTr7EjK0KQWa9vXzs6W24s09mXZMli0axYobcuB
  GuFwt4RCfxARGOdyu8J9tg-QziFPX3TwIVMsAxKLdDZkMcy1fEyXW_A0H3z6IcKzvjxFRl10cASRmNb
  wSGHk8C1W89HLbeeL_wx_Bg6qTpMBjBqnpGYMVG9vSy-fmKDPd6Uysw3DvvOYkX5pFGkln6X5F2fcdxw
  VaV05IGUyxsX-Je4UY671P54ScRVlEXTAQpnmhCiR4euadvPmKqNtk70n02ExAkMJ5UbyS2mcupPjXk7C67
  qVu2kfVMLFFdg1AKhBzlpl-KqvTsgO2-bHYpKS9qwLP1CdKw_1NxB9cvjBmGQ3S33P6WjEg_eNM.rZ1h6
  s-rQb2D_PX8dIYb5g
Accept=*/*
Content-Type=application/json;charset=UTF-8

Incremental authorization

We recommend requesting scopes as needed. It is generally a best practice to request scopes incrementally, at the time access is required, rather than up front. Each time you change the set of scopes you must initiate the authorization workflow again with the new list of scopes and you must get new access and refresh tokens. 

When adding scopes to your authorization, you must send the full complement of scopes you need at that point in your app. For example, an app that wants to support purchases should not request QuickBooks Payments API access until the user presses the Buy button. That is, in the request for a new authorization code you add com.intuit.quickbooks.payment to your list of existing scopes.

About anti-forgery state tokens

You must protect the security of your users by preventing request forgery attacks. The first step is creating a unique session token that holds state between your app and the user's client. You later match this unique session token with the authentication response returned by the Intuit OAuth Login service to verify that the user is making the request and not a malicious attacker. These tokens are often referred to as cross-site request forgery (CSRF) tokens.

One good choice for a state token is a string of 30 or so characters constructed using a high-quality random-number generator. Another is a hash generated by signing some of your session state variables with a key that is kept secret on your back-end.

Revoking access

Your app can programmatically revoke access given to it by a specific user. Use the revoke endpoint to request permissions granted to the application to be removed. 

Making the request

Retrieve the base URI for the revoke endpoint from the discovery document using the key, token_endpoint. The discussion here assumes the base URI is:

POST https://developer.api/intuit.com/v2/oauth2/tokens/revoke

 The actual request might look like the following:

POST https://developer.api/intuit.com/v2/oauth2/tokens/revoke HTTP/1.1
Accept: application/json
Authorization: Basic UTM0dVBvRDIwanp2OUdxNXE1dmlMemppcTlwM1d2
    NzRUdDNReGkwZVNTTDhFRWwxb0g6VEh0WEJlR3dheEtZSlVNaFhzeGxma1l
    XaFg3ZlFlRzFtN2szTFRwbw==
Content-Type: application/json
{
"token": "{bearerToken or refreshToken}"
}

Generating the authorization header 

Calculate the authorization header value as follows:

"Basic " + base64encode(client_id + ":" + client_secret)

where client_id and client_secret are located on the Keys the tab on the app profile via My Apps on the developer site. There are two versions of these keys:

  • Development keys—use only in the sandbox environment.
  • Production keys—use only in the production environment.

Handling the response

This request returns and empty response body and one of the following status codes:

CodeDescription
200Successful revoke.
400One or more of BearerToken, RefreshToken, ClientId or,ClientSecret are incorrect.
401Bad authorization header or no authorization header sent.
500Intuit server internal error, not the fault of the developer.

Discovery document

The OAuth 2.0 and OpendID Connect workflows require the use of multiple endpoints for authenticating users, and for requesting resources including tokens, user information, and public keys.

To simplify the implementation and increase flexibility, both OAuth 2.0 and OpenID Connect workflows allow for the use of a discovery document: a JSON document found at a well known location containing key-value pairs that provide details about OAuth 2.0 and OpenID Connect configurations, including the URIs of the authorization, token, user info, and public-keys endpoints. The discovery documents for Intuit's OAuth 2.0 and OpenID Connect services are located here (these files apply to both OAuth 2.0 and OpenID Connect workflows, even though their filenames may imply an association with OpenID Connect, only):

Here is an example of a discovery document; the field names are those specified in OpenID Connect Discovery 1.0 (refer to this document for both OAuth 2.0 and OpenID Connect workflows). The values here are illustrative, although they are copied from from a recent version of the actual Intuit discover document, they may have changed. Always refer to the actual document for up-to-date information.

{ 
   issuer:"https://oauth.platform.intuit.com/op/v1",
   authorization_endpoint:"https://appcenter.intuit.com/connect/oauth2",
   token_endpoint:"https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
   userinfo_endpoint:"https://accounts.intuit.com/v1/openid_connect/userinfo",
   revocation_endpoint:"https://developer.api.intuit.com/v2/oauth2/tokens/revoke",
   jwks_uri:"https://oauth.platform.intuit.com/op/v1/jwks",
   response_types_supported:[ 
      "code"
   ],
   subject_types_supported:[ 
      "public"
   ],
   id_token_signing_alg_values_supported:[ 
      "RS256"
   ],
   scopes_supported:[ 
      "openid",
      "email",
      "profile",
      "address",
      "phone"
   ],
   token_endpoint_auth_methods_supported:[ 
      "client_secret_post",
      "client_secret_basic"
   ],
   claims_supported:[ 
      "aud",
      "exp",
      "iat",
      "iss",
      "realmid",
      "sub"
   ]
}

Security guidelines

 Got Questions? Get Answers in our developer forums.