intuit-oauth
Version:
Intuit Node.js client for OAuth2.0 and OpenIDConnect
1,093 lines (870 loc) • 34.9 kB
Markdown
[][ss1]
[](https://travis-ci.org/intuit/oauth-jsclient?branch=master)
[](https://www.npmjs.com/package/intuit-oauth)
[](https://coveralls.io/github/intuit/oauth-jsclient?branch=master)
[](https://github.com/intuit/oauth-jsclient/graphs/contributors)
[](https://scrutinizer-ci.com/g/intuit/oauth-jsclient/?branch=master)

[](https://github.com/prettier/prettier)
[](https://snyk.io/test/github/intuit/oauth-jsclient)
# OAuth Client for Intuit
A Node.js client for Intuit's OAuth 2.0 implementation.
## Features
- OAuth 2.0 authentication flow
- Token management and refresh
- API request handling with automatic base URL resolution
- **Comprehensive error handling** with custom error types (OAuthError, ValidationError, TokenError)
- **Full HTTP status code support** (400, 401, 403, 404, 429, 500, 502, 503, 504)
- **QuickBooks Fault object parsing** for detailed validation errors
- Automatic retry for transient errors with exponential backoff
- Structured logging with Winston
- Response validation and transformation
## Installation
```bash
npm install intuit-oauth
```
## Usage
```javascript
const OAuthClient = require('intuit-oauth');
const oauthClient = new OAuthClient({
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
environment: 'sandbox', // or 'production'
redirectUri: 'http://localhost:8000/callback',
logging: true // Enable logging
});
```
## Error Handling
The client provides comprehensive error handling with custom error types and full HTTP status code support:
### Error Types
- **`OAuthError`**: Base error class for all OAuth and API errors
- Includes error code, description, and Intuit transaction ID
- Supports QuickBooks Fault object details
- Available for all HTTP status codes (400, 401, 403, 404, 429, 500, 502, 503, 504)
- **`ValidationError`**: For input validation errors
- **`TokenError`**: For token-related errors (expired, invalid, etc.)
- **`NetworkError`**: For network connectivity issues
All errors include detailed debugging information and are properly thrown (never returned as successful responses).
### OAuth2 Error Response Handling
When OAuth operations fail (e.g., `createToken()`, `refresh()`), the library now properly surfaces the full error response from QuickBooks:
```javascript
try {
const authResponse = await oauthClient.createToken(code);
const token = authResponse.getToken();
} catch (error) {
// Access detailed OAuth2 error information
console.error('OAuth Error:', error.error); // e.g., "invalid_grant"
console.error('Description:', error.error_description); // e.g., "Token invalid"
console.error('Transaction ID:', error.intuit_tid); // For debugging with QuickBooks support
console.error('Full response:', error.authResponse.json); // Complete error payload
// Handle specific OAuth2 errors
if (error.error === 'invalid_grant') {
// Authorization code expired or invalid - redirect user to re-authorize
} else if (error.error === 'invalid_client') {
// Client credentials are invalid - check configuration
}
}
```
### Error Handling Demo
Try the interactive error handling demo to see how OAuth2 errors are surfaced:
```bash
node test/error-handling-demo.js
```
This demo script demonstrates:
- How OAuth2 error responses are captured and surfaced
- Full error details including error codes, descriptions, and transaction IDs
- Programmatic error handling based on error types
Example error handling:
```javascript
try {
await oauthClient.makeApiCall({ url: 'https://api.example.com' });
} catch (error) {
if (error instanceof TokenError) {
// Handle token errors
console.error('Token error:', error.code, error.description);
} else if (error instanceof NetworkError) {
// Handle network errors
console.error('Network error:', error.message);
} else if (error instanceof ValidationError) {
// Handle validation errors
console.error('Validation error:', error.message);
} else {
// Handle other errors
console.error('Unexpected error:', error);
}
}
```
### Common OAuth2 Errors
The library properly handles and surfaces these QuickBooks OAuth2 errors:
| Error Code | Description | Typical Cause | Recommended Action |
|------------|-------------|---------------|-------------------|
| `invalid_grant` | Authorization grant is invalid, expired, or revoked | User needs to re-authorize | Redirect user to authorization URL |
| `invalid_client` | Client authentication failed | Invalid clientId/clientSecret | Verify OAuth credentials in config |
| `invalid_request` | Request is malformed or invalid | Incorrect request parameters | Check authorization code and redirect URI |
| `unauthorized_client` | Client is not authorized | Missing required permissions | Check app configuration and scopes |
| `unsupported_grant_type` | Grant type not supported | Wrong grant_type parameter | Use 'authorization_code' or 'refresh_token' |
### HTTP Status Code Handling
The library includes comprehensive error handling for all HTTP status codes:
| Status Code | Error Type | Description |
|-------------|------------|-------------|
| `400` | Bad Request | Validation errors, includes QuickBooks Fault object details |
| `401` | Unauthorized | Invalid or expired access token |
| `403` | Forbidden | Insufficient permissions |
| `404` | Not Found | Resource not found |
| `429` | Rate Limited | Too many requests |
| `500` | Internal Server Error | Server-side error |
| `502` | Bad Gateway | Gateway error |
| `503` | Service Unavailable | Service temporarily unavailable |
| `504` | Gateway Timeout | Gateway timeout |
All errors include:
- Detailed error messages
- HTTP status codes
- Transaction IDs (`intuit_tid`) for support
- Full response data for debugging
### QuickBooks Fault Object Handling
When QuickBooks API returns validation errors, the library properly parses and surfaces Fault objects:
```javascript
try {
await oauthClient.makeApiCall({
url: '/v3/company/123/customer',
method: 'POST',
body: invalidData
});
} catch (error) {
// Access Fault object details
if (error.fault) {
console.log('Fault Type:', error.fault.type); // e.g., "ValidationFault"
console.log('Errors:', error.fault.errors); // Array of error details
error.fault.errors.forEach(err => {
console.log('Message:', err.message);
console.log('Detail:', err.detail);
console.log('Code:', err.code);
});
}
}
```
### Debugging OAuth Errors
When an OAuth error occurs, use these debugging strategies:
1. **Log full error details**:
```javascript
console.log('Error Code:', error.error);
console.log('Description:', error.error_description);
console.log('Transaction ID:', error.intuit_tid);
console.log('Full Response:', JSON.stringify(error.authResponse.json, null, 2));
```
2. **Use the Transaction ID**: Contact QuickBooks support with the `intuit_tid` from the error for detailed troubleshooting
3. **Check error code**: Different error codes require different handling strategies (see table above)
4. **Examine response body**: `error.authResponse.body` contains the raw response string for detailed debugging
5. **Enable logging**: Set `logging: true` in OAuthClient config to capture detailed logs in `logs/oAuthClient-log.log`
## Retry Logic
The client includes automatic retry logic for transient errors:
- Maximum 3 retries
- Exponential backoff (1s, 2s, 4s)
- Retries on specific status codes (408, 429, 500, 502, 503, 504)
- Retries on network errors (ECONNRESET, ETIMEDOUT, ECONNREFUSED)
You can configure retry behavior:
```javascript
OAuthClient.retryConfig = {
maxRetries: 3,
retryDelay: 1000,
retryableStatusCodes: [408, 429, 500, 502, 503, 504],
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED']
};
```
## Logging
The client provides structured logging when enabled:
```javascript
const oauthClient = new OAuthClient({
// ... other config
logging: true
});
```
Log entries include:
- Timestamp
- Log level
- Message
- Request context (URL, method, headers)
- Error details (for error logs)
- Environment information
- Client ID
Example log entry:
```json
{
"timestamp": "2024-03-14T12:00:00.000Z",
"level": "error",
"message": "API call failed",
"data": {
"error": {
"name": "TokenError",
"code": "UNAUTHORIZED",
"message": "Invalid or expired access token",
"stack": "...",
"intuit_tid": "1234-1234-1234-123"
}
},
"environment": "sandbox",
"clientId": "your_client_id",
"request": {
"url": "https://api.example.com",
"method": "GET",
"headers": {
"Authorization": "Bearer ...",
"Accept": "application/json"
}
}
}
```
## Response Validation
The client validates responses and throws appropriate errors for common scenarios:
- 401 Unauthorized: Invalid or expired access token
- 403 Forbidden: Insufficient permissions
- 429 Too Many Requests: Rate limit exceeded
- Missing or invalid response data
- Invalid content types
## API Reference
### OAuthClient
#### constructor(config)
Creates a new OAuthClient instance.
```javascript
const oauthClient = new OAuthClient({
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
environment: 'sandbox',
redirectUri: 'http://localhost:8000/callback',
logging: true
});
```
#### makeApiCall(params)
Makes an API call with automatic retry and error handling.
```javascript
const response = await oauthClient.makeApiCall({
url: 'https://api.example.com',
method: 'GET',
headers: {
'Custom-Header': 'value'
},
body: {
key: 'value'
}
});
// Access response data - both formats supported for backward compatibility
console.log(response.data); // Recommended (works in all versions)
console.log(response.json); // Also supported (4.2.1+)
// Response includes:
// - response.data: Response data (all versions)
// - response.json: Response data (4.2.1+, same as response.data)
// - response.body: Response as string
// - response.status: HTTP status code
// - response.statusText: HTTP status text
// - response.headers: Response headers
```
**Response Format Note**: Version 4.2.1 introduced a breaking change that removed `response.data` in favor of `response.json`. Version 4.2.3 restored `response.data` for backward compatibility. Both `response.data` and `response.json` now return the same value, so you can use either.
#### validateResponse(response)
Validates an API response and throws appropriate errors.
```javascript
try {
oauthClient.validateResponse(response);
} catch (error) {
// Handle validation errors
}
```
## License
Apache License 2.0
# Intuit OAuth2.0 NodeJS Library
The OAuth2 Nodejs Client library is meant to work with Intuit's
[OAuth2.0](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0)
and
[OpenID Connect](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/openid-connect)
implementations which conforms to the specifications.
## Table of Contents
- [Intuit OAuth2.0 NodeJS Library](#intuit-oauth20-nodejs-library)
- [Table of Contents](#table-of-contents)
- [Requirements](#requirements)
- [Installation](#installation)
- [Using NodeJS](#using-nodejs)
- [Options :](#options)
- [Usage](#usage)
- [Authorization Code Flow](#authorization-code-flow)
- [Step 1](#step-1)
- [Scopes :](#scopes)
- [Step 2](#step-2)
- [Sample](#sample)
- [Helpers](#helpers)
- [Is AccessToken Valid](#is-accesstoken-valid)
- [Refresh access_token](#refresh-access_token)
- [Refresh access_token by passing the refresh_token explicitly](#refresh-access_token-by-passing-the-refresh_token-explicitly)
- [Revoke access_token](#retrieve-the-token-)
- [Getter / Setter for Token](#getter--setter-for-token)
- [Retrieve the Token :](#retrieve-the-token)
- [Set the Token :](#set-the-token-)
- [Migrate OAuth1.0 Tokens to OAuth2.0](#migrate-oauth10-tokens-to-oauth20)
- [Validate ID Token](#validate-id-token)
- [Make API call](#make-api-call)
- [Auth-Response](#auth-response)
- [Error Logging](#error-logging)
- [FAQ](#faq)
- [Contributing](#contributing)
- [Steps](#steps)
- [Changelog](#changelog)
- [License](#license)
# Requirements
The Node.js client library is tested against the `Node 10` and newer versions.
| Version | Node support |
|----------------------------------------------------------------------------------|-----------------------------------|
| [intuit-oauth@1.x.x](https://github.com/intuit/oauth-jsclient/tree/1.5.0) | Node 6.x or higher |
| [intuit-oauth@2.x.x](https://github.com/intuit/oauth-jsclient/tree/2.0.0) | Node 7.x or higher |
| [intuit-oauth@3.x.x](https://github.com/intuit/oauth-jsclient/tree/3.0.2) | Node 8.x or Node 9.x and higher |
**Note**: Older node versions are not supported.
# Installation
Follow the instructions below to use the library :
## Using NodeJS
1. Install the NPM package:
```sh
npm install intuit-oauth --save
```
2. Require the Library:
```js
const OAuthClient = require('intuit-oauth');
const oauthClient = new OAuthClient({
clientId: '<Enter your clientId>',
clientSecret: '<Enter your clientSecret>',
environment: 'sandbox' || 'production',
redirectUri: '<Enter your callback URL>',
});
```
### Options
- `clientId` - clientID for your app. Required
- `clientSecret` - clientSecret fpor your app. Required
- `environment` - environment for the client. Required
- `sandbox` - for authorizing in sandbox.
- `production` - for authorizing in production.
- `redirectUri` - redirectUri on your app to get the `authorizationCode` from Intuit Servers. Make sure this redirect URI is also added on your app in the [developer portal](https://developer.intuit.com) on the Keys & OAuth tab. Required
- `logging` - by default, logging is disabled i.e `false`. To enable provide`true`.
# Usage
We assume that you have a basic understanding about OAuth2.0. If not please read
[API Documentation](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0)
for clear understanding
## Authorization Code Flow
The Authorization Code flow is made up of two parts :
**Step 1.** Redirect user to `oauthClient.authorizeUri(options)`.
**Step 2.** Parse response uri and get access-token using the function
`oauthClient.createToken(req.url)` which returns a
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
### Step 1
```javascript
// Instance of client
const oauthClient = new OAuthClient({
clientId: '<Enter your clientId>',
clientSecret: '<Enter your clientSecret>',
environment: 'sandbox',
redirectUri: '<http://localhost:8000/callback>',
});
// AuthorizationUri
const authUri = oauthClient.authorizeUri({
scope: [OAuthClient.scopes.Accounting, OAuthClient.scopes.OpenId],
state: 'testState',
}); // can be an array of multiple scopes ex : {scope:[OAuthClient.scopes.Accounting,OAuthClient.scopes.OpenId]}
// Redirect the authUri
res.redirect(authUri);
```
### Scopes
The available scopes include :
- `com.intuit.quickbooks.accounting` - for accounting scope include `OAuthClient.scopes.Accounting`
- `com.intuit.quickbooks.payment` - for payment scope include `OAuthClient.scopes.Payment`
- `com.intuit.quickbooks.payroll` - for QuickBooks Payroll API (whitelisted beta apps only)
- `com.intuit.quickbooks.payroll.timetracking` - for QuickBooks Payroll API for for access to
compensation (whitelisted beta apps only)
- `com.intuit.quickbooks.payroll.benefits` - for QuickBooks Payroll API for access to
benefits/pension/deduction (whitelisted beta apps only)
OpenID Scopes :
- `openid` - for openID assertion include `OAuthClient.scopes.OpenId`
- `profile` - for profile assertion include `OAuthClient.scopes.Profile`
- `email` - for email assertion include `OAuthClient.scopes.Email`
- `phone` - for phone assertion include `OAuthClient.scopes.Phone`
- `address` - for address assertion include `OAuthClient.scopes.Address`
### Step 2
```javascript
// Parse the redirect URL for authCode and exchange them for tokens
const parseRedirect = req.url;
// Exchange the auth code retrieved from the **req.url** on the redirectUri
oauthClient
.createToken(parseRedirect)
.then(function (authResponse) {
console.log('The Token is ' + JSON.stringify(authResponse.getToken()));
})
.catch(function (e) {
console.error('The error message is :' + e.originalMessage);
console.error(e.intuit_tid);
});
```
# Sample
For more clarity, we suggest you take a look at the sample application below :
[sample](https://github.com/intuit/oauth-jsclient/tree/master/sample)
## Helpers
### Is AccessToken Valid
You can check if the `access_token` associated with the `oauthClient` is valid ( not expired ) or
not using the helper method.
```javascript
if (oauthClient.isAccessTokenValid()) {
console.log('The access_token is valid');
}
if (!oauthClient.isAccessTokenValid()) {
oauthClient
.refresh()
.then(function (authResponse) {
console.log('Tokens refreshed : ' + JSON.stringify(authResponse.getToken()));
})
.catch(function (e) {
console.error('The error message is :' + e.originalMessage);
console.error(e.intuit_tid);
});
}
```
\*\* Note: If the access_token is not valid, you can call the client's `refresh()` method to refresh
the tokens for you as shown below
### Refresh 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. 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.
```javascript
oauthClient
.refresh()
.then(function (authResponse) {
console.log('Tokens refreshed : ' + JSON.stringify(authResponse.getToken()));
})
.catch(function (e) {
console.error('The error message is :' + e.originalMessage);
console.error(e.intuit_tid);
});
```
### Refresh access_token by passing the refresh_token explicitly
You can call the below helper method to refresh tokens by explictly passing the refresh_token.
\*\*Note : `refresh_token` should be of the type `string`
```javascript
oauthClient
.refreshUsingToken('<Enter the refresh token>')
.then(function (authResponse) {
console.log('Tokens refreshed : ' + JSON.stringify(authResponse.getToken()));
})
.catch(function (e) {
console.error('The error message is :' + e.originalMessage);
console.error(e.intuit_tid);
});
```
### Revoke access_token
When you no longer need the access_token, you could use the below helper method to revoke the
tokens.
```javascript
oauthClient
.revoke()
.then(function (authResponse) {
console.log('Tokens revoked : ' + JSON.stringify(authResponse.json));
})
.catch(function (e) {
console.error('The error message is :' + e.originalMessage);
console.error(e.intuit_tid);
});
```
Alternatively you can also pass `access_token` or `refresh_token` to this helper method using the
`params` object: refer to - [Getter / Setter for Token](#getter--setter-for-token) section to know
how to retrieve the `token` object
```javascript
oauthClient
.revoke(params)
.then(function (authResponse) {
console.log('Tokens revoked : ' + JSON.stringify(authResponse.json));
})
.catch(function (e) {
console.error('The error message is :' + e.originalMessage);
console.error(e.intuit_tid);
});
```
** Note ** : `params` is the Token JSON object as shown below : ( _If you do not pass the `params`
then the token object of the client would be considered._)
```
{
"token_type": "bearer",
"expires_in": 3600,
"refresh_token":"<refresh_token>",
"x_refresh_token_expires_in":15552000,
"access_token":"<access_token>",
"createdAt": "(Optional Default = Date.now()) <Milliseconds> from the unix epoch"
}
```
** Note ** :
### Getter / Setter for Token
You can call the below methods to set and get the tokens using the `oauthClient` instance:
#### Retrieve the Token :
```javascript
// To get the tokens
let authToken = oauthClient.getToken().getToken();
`OR`;
let authToken = oauthClient.token.getToken();
```
#### Set the Token :
```javascript
// To Set the retrieved tokens explicitly using Token Object but the same instance
oauthClient.setToken(authToken);
OR;
// To set the retrieved tokens using a new client instance
const oauthClient = new OAuthClient({
clientId: '<Enter your clientId>',
clientSecret: '<Enter your clientSecret>',
environment: 'sandbox',
redirectUri: '<http://localhost:8000/callback>',
token: authToken,
});
```
The authToken parameters are as follows:
```
{
token_type: '<String>',
access_token: '<String>',
expires_in: '<Int> Seconds',
refresh_token: '<String>',
x_refresh_token_expires_in: '<Int> Seconds',
id_token: "(Optional Default = '') <String>",
createdAt: '(Optional Default = Date.now()) <Milliseconds> from the unix epoch'
}
```
**Note** :
The OAuth Client library converts the accessToken and refreshToken expiry time to `TimeStamp`. If
you are setting a stored token, please pass in the `createdAt` for accurate experiations.
```javascript
oauthClient.setToken(authToken);
```
### Migrate OAuth1.0 Tokens to OAuth2.0
You can call the below method to migrate the bearer / refresh tokens from OAuth1.0 to OAuth2.0. You
```javascript
// Fill in the params object ( argument to the migrate function )
let params = {
oauth_consumer_key: '<Enter oauth1ConsumerKey>',
oauth_consumer_secret: '<Enter oauth1ConsumerSecret>',
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.round(new Date().getTime() / 1000),
oauth_nonce: 'nonce',
oauth_version: '1.0',
access_token: '<Enter OAuth1.0 access_token>',
access_secret: '<Enter OAuth1.0 access_secret>',
scope: [OAuthClient.scopes.Accounting],
};
oauthClient
.migrate(params)
.then(function (response) {
console.log('The response is ' + JSON.stringify(response));
})
.catch(function (e) {
console.log('The error is ' + e.message);
});
```
### Validate ID Token
You can validate the ID token obtained from `Intuit Authorization Server` as shown below :
```javascript
oauthClient
.validateIdToken()
.then(function (response) {
console.log('Is my ID token validated : ' + response);
})
.catch(function (e) {
console.log('The error is ' + JSON.stringify(e));
});
// Is my ID token validated : true
```
The client validates the ID Token and returns boolean `true` if validates successfully else it would
throw an exception.
### Make API Call
You can make API call using the token generated from the client as shown below :
```javascript
// Body sample from API explorer examples
const body = {
TrackQtyOnHand: true,
Name: 'Garden Supplies',
QtyOnHand: 10,
InvStartDate: '2015-01-01',
Type: 'Inventory',
IncomeAccountRef: {
name: 'Sales of Product Income',
value: '79',
},
AssetAccountRef: {
name: 'Inventory Asset',
value: '81',
},
ExpenseAccountRef: {
name: 'Cost of Goods Sold',
value: '80',
},
};
oauthClient
.makeApiCall({
url: 'https://sandbox-quickbooks.api.intuit.com/v3/company/1234/item',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then(function (response) {
console.log('The API response is : ' + response);
})
.catch(function (e) {
console.log('The error is ' + JSON.stringify(e));
});
```
You can also make the calling using the endpoint path:
```javascript
// Body sample from API explorer examples
const body = {
TrackQtyOnHand: true,
Name: 'Garden Supplies',
QtyOnHand: 10,
InvStartDate: '2015-01-01',
Type: 'Inventory',
IncomeAccountRef: {
name: 'Sales of Product Income',
value: '79',
},
AssetAccountRef: {
name: 'Inventory Asset',
value: '81',
},
ExpenseAccountRef: {
name: 'Cost of Goods Sold',
value: '80',
},
};
oauthClient
.makeApiCall({
url: '/v3/company/1234/item',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then(function (response) {
console.log('The API response is : ' + response);
})
.catch(function (e) {
console.log('The error is ' + JSON.stringify(e));
});
```
The client validates the ID Token and returns boolean `true` if validates successfully else it would
throw an exception.
#### Support for PDF format
In order to save the PDF generated from the APIs properly, the correct transport type should be passed into the `makeAPI()`.Below is an example of the same:
```
.makeApiCall({ url: `${url}v3/company/${companyID}/invoice/${invoiceNumber}/pdf?minorversion=59` , headers:{'Content-Type': 'application/pdf','Accept':'application/pdf'}, transport: popsicle.createTransport({type: 'buffer'})})
```
The response is an actual buffer( binary BLOB) which could then be saved to the file.
### Auth-Response
The response provided by the client is a wrapped response of the below items which is what we call
authResponse, lets see how it looks like:
```text
1. response // response from `HTTP Client` used by library
2. token // instance of `Token` Object
3. body // res.body in `text`
4. json // res.body in `JSON`
5. intuit_tid // `intuit-tid` from response headers
```
A sample `AuthResponse` object would look similar to :
```json
{
"token": {
"realmId": "<realmId>",
"token_type": "bearer",
"access_token": "<access_token>",
"refresh_token": "<refresh_token>",
"expires_in": 3600,
"x_refresh_token_expires_in": 8726400,
"id_token": "<id_token>",
"latency": 60000
},
"response": {
"url": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
"headers": {
"content-type": "application/json;charset=UTF-8",
"content-length": "61",
"connection": "close",
"server": "nginx",
"strict-transport-security": "max-age=15552000",
"intuit_tid": "1234-1234-1234-123",
"cache-control": "no-cache, no-store",
"pragma": "no-cache"
},
"body": "{\"id_token\":\"<id_token>\",\"expires_in\":3600,\"token_type\":\"bearer\",\"x_refresh_token_expires_in\":8726400,\"refresh_token\":\"<refresh_token>\",\"access_token\":\"<access_token>\"}",
"status": 200,
"statusText": "OK"
},
"body": "{\"id_token\":\"<id_token>\",\"expires_in\":3600,\"token_type\":\"bearer\",\"x_refresh_token_expires_in\":8726400,\"refresh_token\":\"<refresh_token>\",\"access_token\":\"<access_token>\"}",
"json": {
"access_token": "<access_token>",
"refresh_token": "<refresh_token>",
"token_type": "bearer",
"expires_in": "3600",
"x_refresh_token_expires_in": "8726400",
"id_token": "<id_token>"
},
"intuit_tid": "4245c696-3710-1548-d1e0-d85918e22ebe"
}
```
You can use the below helper methods to make full use of the Auth Response Object :
```javascript
oauthClient.createToken(parseRedirect).then(function (authResponse) {
console.log('The Token in JSON is ' + JSON.stringify(authResponse.json));
let status = authResponse.status();
let body = authResponse.text();
let jsonResponse = authResponse.json;
let intuit_tid = authResponse.get_intuit_tid();
});
```
### Error Logging
By default the logging is `disabled` i.e set to `false`. However, to enable logging, pass
`logging=true` when you create the `oauthClient` instance :
```javascript
const oauthClient = new OAuthClient({
clientId: '<Enter your clientId>',
clientSecret: '<Enter your clientSecret>',
environment: 'sandbox',
redirectUri: '<http://localhost:8000/callback>',
logging: true,
});
```
The logs would be captured under the directory `/logs/oAuthClient-log.log`
Whenever there is an error, the library throws an exception and you can use the below helper methods
to retrieve more information :
```javascript
oauthClient.createToken(parseRedirect).catch(function (error) {
console.log(error);
});
/**
* This is how the Error Object Looks :
{
"originalMessage":"Response has an Error",
"error":"invalid_grant",
"error_description":"Token invalid",
"intuit_tid":"4245c696-3710-1548-d1e0-d85918e22ebe"
}
*/
```
## FAQ
### Common Issues
#### API calls fail after upgrading to version 4.2.1 or 4.2.2
**Problem 1**: After upgrading from version 4.2.0 to 4.2.1, API calls started failing with `response.data is undefined`.
**Cause**: Version 4.2.1 introduced an undocumented breaking change that removed `response.data` and replaced it with `response.json`.
**Solution**: Upgrade to version 4.2.3 or later, which restores `response.data` for backward compatibility. Both `response.data` and `response.json` are now available:
```javascript
const response = await oauthClient.makeApiCall({ url: '/v3/company/123/item' });
console.log(response.data); // ✅ Works in 4.2.0, 4.2.3+
console.log(response.json); // ✅ Works in 4.2.1+
```
**Problem 2**: After upgrading from version 4.2.0 to 4.2.1, API calls started failing with malformed header errors.
**Cause**: Version 4.2.1 had a bug in the `makeApiCall` method where the Authorization header was incorrectly constructed, causing HTTP requests to have invalid headers.
**Solution**: Upgrade to version 4.2.2 or later, which fixes this issue. The fix ensures the Authorization header is properly set as:
```javascript
Authorization: `Bearer ${access_token}`
```
#### How do I enable logging?
Pass `logging: true` when creating the OAuthClient instance:
```javascript
const oauthClient = new OAuthClient({
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
environment: 'sandbox',
redirectUri: 'http://localhost:8000/callback',
logging: true // Enable logging
});
```
Logs will be stored in `/logs/oAuthClient-log.log`
#### How do I handle token expiration?
Access tokens expire after 1 hour (3600 seconds). You can check token validity and refresh:
```javascript
if (!oauthClient.isAccessTokenValid()) {
await oauthClient.refresh();
}
```
#### What's the difference between relative and absolute URLs in makeApiCall?
You can use either format:
- **Absolute URL**: `https://sandbox-quickbooks.api.intuit.com/v3/company/123/item`
- **Relative URL**: `/v3/company/123/item` (the client will automatically prepend the correct base URL based on your environment)
#### How do I customize retry behavior?
Configure the retry settings:
```javascript
OAuthClient.retryConfig = {
maxRetries: 3,
retryDelay: 1000, // milliseconds
retryableStatusCodes: [408, 429, 500, 502, 503, 504],
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED']
};
```
#### How do I handle OAuth errors like invalid_grant?
As of version 4.2.2, the library properly surfaces OAuth2 error details. When an OAuth operation fails, you can access:
```javascript
try {
const authResponse = await oauthClient.createToken(code);
} catch (error) {
console.log('Error:', error.error); // e.g., "invalid_grant"
console.log('Description:', error.error_description); // e.g., "Token invalid"
console.log('Transaction ID:', error.intuit_tid); // For support
// Handle specific errors
if (error.error === 'invalid_grant') {
// Redirect user to re-authorize
}
}
```
Run the demo to see error handling in action:
```bash
node test/error-handling-demo.js
```
See the [Error Handling](#error-handling) section for complete details.
#### Does createToken return errors or throw them?
`createToken()` **always throws errors** and never returns them as successful results:
✅ **On Success**: Returns an `AuthResponse` object with token data
```javascript
const authResponse = await oauthClient.createToken(callbackUrl);
const token = authResponse.getToken(); // Access token data
```
❌ **On Error**: Throws an exception (never returns it)
```javascript
try {
const authResponse = await oauthClient.createToken(callbackUrl);
// Success - use authResponse
} catch (error) {
// Error thrown - handle it
console.error(error.error, error.error_description);
}
```
The library has comprehensive tests (131+ test cases) that verify this behavior is always correct.
For more questions, refer to our [FAQ wiki](https://github.com/intuit/oauth-jsclient/wiki/FAQ).
## Contributing
- You are welcome to send a PR to `develop` branch.
- The `master` branch will always point to the latest published version.
- The `develop` branch will contain the latest development/testing changes.
### Steps
- Fork and clone the repository (`develop` branch).
- Run `npm install` for dependencies.
- Run `npm test` to execute all specs.
### Test Coverage
The library maintains comprehensive test coverage.
#### Test Categories
1. **OAuth Flow Tests**: Authorization, token creation, refresh, and revocation
2. **Error Handling Tests**: HTTP status codes (400, 401, 403, 404, 429, 500, 502, 503, 504)
3. **Fault Object Tests**: QuickBooks Fault response handling
4. **Token Management Tests**: Token validation, expiration, and lifecycle
5. **API Call Tests**: Request handling, retries, and response processing
6. **Response Processing Tests**: AuthResponse object creation and data transformation
Run tests with coverage report:
```bash
npm test
```
View detailed HTML coverage report:
```bash
npm test && open coverage/index.html
```
## Changelog
See the changelog [here](https://github.com/intuit/oauth-jsclient/blob/master/CHANGELOG.md)
## License
Intuit `oauth-jsclient` is licensed under the
[Apache License, Version 2.0](https://github.com/intuit/oauth-jsclient/blob/master/LICENSE)
[ss1]: https://help.developer.intuit.com/s/SDKFeedback?cid=1120