If you want your app to use Intuit Single Sign-on for authorization, you can add additional functionality after you implement OAuth 2.0.
We’ll show you how to modify your existing implementation so it conforms with the OpenID specification and is thus OpenID certified.
The set up process is very similar to OAuth 2.0, but the key difference is the type of tokens used. Your app needs to authenticate users by obtaining and validating ID tokens. This additional authentication ensures that apps that use our single sign-on provider conform to the OpenID Connect spec.
One of the primary differences between standard OAuth 2.0 authentication and OpenID Connect is the required scopes.
OAuth 2.0 in general supports the two main scopes for the QuickBooks Online Accounting API (com.intuit.quickbooks.accounting) and QuickBooks Payments API (com.intuit.quickbooks.payment).
OpenID Connect adds a few additional scopes:
If you haven’t already, follow the steps to implement OAuth 2.0.
Once you get to the “Exchange the authorization code for access tokens”step, come back to these OpenID Connect steps.
To authenticate users via OpenID Connect, your app needs to get and then validate ID tokens. ID tokens are a standardized feature of OpenID Connect for sharing identity assertions over the web.
While authorization is a simple one-step process for users, it involves several tasks on the backend. Here’s a brief overview of the authorization flow for OpenID Connect:
Once you get access and ID tokens, your app can make API calls.
If you’ve followed the OAuth 2.0 set up and your app sends an authorization request when users connect, it will get an authorization code from the Intuit OAuth 2.0 Server. You can also leverage claims
query parameter that is returned in the authorization response to get the user profile information (The realm ID of the specific QuickBooks company to which the user is associated). More info on Claims query parameter
Your app should send the authorization code (i.e. the value of the code
parameter) back to the Intuit OAuth 2.0 server to exchange it for access and ID tokens.
Use the example code to create an object named tokenResponse. This automatically exchanges the authorization code for access and refresh tokens:
.NET
Java
PHP
Node.js
Python
Ruby
1 2 3 4 5 6 7 | // Get OAuth2 Bearer token and ID token var tokenResponse = await auth2Client.GetBearerTokenAsync(code); //retrieve access_token and refresh_token tokenResponse.AccessToken tokenResponse.RefreshToken idToken = tokenResponse.IdentityToken; |
1 2 3 4 5 6 7 8 9 | //Prepare OAuth2PlatformClient OAuth2PlatformClient client = new OAuth2PlatformClient(oauth2Config); //Get the bearer token (OAuth2 tokens) BearerTokenResponse bearerTokenResponse = client.retrieveBearerTokens(authCode, redirectUri); //retrieve the token using the variables below bearerTokenResponse.getAccessToken() bearerTokenResponse.getIdToken() |
1 2 3 | $accessToken = $OAuth2LoginHelper->exchangeAuthorizationCodeForToken("authorizationCode", "realmId"); $accessTokenValue = $accessTokenObj->getAccessToken(); $refreshTokenValue = $accessTokenObj->getRefreshToken(); |
1 2 3 4 5 6 7 8 9 10 11 12 | // Parse the redirect URL for authCode and exchange them for tokens var 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.getJson())); }) .catch(function(e) { console.error("The error message is :"+e.originalMessage); console.error(e.intuit_tid); }); |
1 2 3 4 5 6 7 | # get OAuth2 Bearer token auth_client.get_bearer_token(auth_code, realm_id=realm_id) # retrieve access_token and refresh_token auth_client.access_token auth_client.refresh_token auth_client.id_token |
1 2 | oauth2Token = oauth_client.token.get_bearer_token('the authorization code returned from authorizationCodeUrl') # => #<IntuitOAuth::ClientResponse:0x00007f9152b5c418 @access_token="the access token", @expires_in=3600, @refresh_token="the refresh token", @x_refresh_token_expires_in=8726400> |
Get the base URI from the discovery document. You can also follow these links:
Create a POST request to exchange the authorization code for access and ID tokens.
Send requests to the token_endpoint
(available in the discovery document) using the following parameters. Only send one request to exchange the authorization code. Multiple requests may invalidate tokens:
Field | Description | Required |
---|---|---|
code |
The authorization code your app received from the Intuit OAuth 2.0 response. | Yes |
redirect_uri |
The redirect URI listed for your app. You set this in Step 7. | Yes |
grant_type |
The type defined by the OAuth 2.0 server specification. It must have the value authorization_code. | Yes |
Here’s an example token exchange request:
1 2 3 4 5 6 7 8 9 10 | 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 |
The authorization header should follow this format:
1 | "Basic " + base64encode(client_id + ":" + client_secret) |
The server returns a JSON object with both tokens.
access_token
field.id_token
field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | { "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", "id_token":"eyJraWQiOiJyNHA1U2JMMnFhRmVoRnpoajhnSSIsImFsZyI6IlJTMjU2In0.eyJzd WIiOiJiMDUzZDk5NC0wN2Q1LTQ2OGQtYjdlZS0yMmUzNDlkMmU3MzkiLCJhdWQiOlsiTDM5ZWxTdWJGeGpQT1 NwZFpvWVdSS2lDQ0U2VElOanY2N1JvYUU4ekJxYkl4eGI0bEsiXSwicmVhbG1pZCI6IjExMDgwMzM0Nz EiLCJhdXRoX3RpbWUiOjE0NjI1NTQ0NzUsImlzcyI6Imh0dHBzOlwvXC9vYXV0aC1lMmUucGxhdGZvcm 0uaW50dWl0LmNvbVwvb2F1dGgyXC92MVwvb3BcL3YxIiwiZXhwIjoxNDYyNTYxMzI4LCJpYXQiOjE0NjI1 NTc3Mjh9.BIJ9x_WPEOZsLJfQE3mGji_Q15j_rdlTyFYELiJM-W92fWSLC-TLEwCp5IrRhDWMvyvrLSMZCEd QALYQpbVy8uKI22JgGWYvkwNEDweOjbYzyt33F4xtn3GGcW9nAwRtA3M19qquWyi7G0kcCZUDN8RfUXz2qKM J6KPOfLVe2UQ" } |
Field | Description |
---|---|
access_token |
The token used to access the QuickBooks Online API. Max length: 4096 characters. |
refresh_token |
The token used for refreshing the access token. Max length: 512 characters. |
id_token |
The token used for OpenID Connect and associated scopes for user authentication. |
x_refresh_token_expires_in |
The remaining lifetime of the current refresh token. This is in seconds. When this expires, users must reauthorize your app. |
expires_in |
The remaining lifetime of the access token. The value begins at 3600. This is in seconds (one hour). |
token_type |
Identifies the type of token returned. This always has the value bearer. |
Your app needs to validate all ID tokens on your server unless you know they came directly from Intuit.
Since most external API libraries also validate when decoding the base64 and parsing the JSON, your app may automatically validate when it accesses the fields on the ID token.
You can also review the official OpenID validation specs.
If you use our supported SDKs, we handle the validation process for you. Here are some examples:
.NET
Java
PHP
Node.js
Python
Ruby
1 | var isTokenValid = await oauthClient.ValidateIDTokenAsync(tokenResponse.IdentityToken); |
1 | boolean valid = client.validateIDToken(bearerTokenResponse.getIdToken()); |
1 2 | //Return true if it is correct, or false otherwise $result = $OAuthLoginHelper::ValidateIDToken($clientID, $ID_token); |
1 2 3 4 5 6 7 8 9 | 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 |
1 2 | # Python client sets id_token value only after validating it # No additional step needed for validation |
1 | validated = validate_id_token(id_token) |
Apps need to include signatures in requests to ensure requests sent between your app, our server, and connected QuickBooks Online companies aren’t changed during transit.
Thus, your app needs to verify that the signing authority for responses are from Intuit. Here are a few ways you can check signatures:
kid
value should match the value that returned in the ID token header.e
field and modulo m
field values to create the public key and then validate the signature.You can create and use a JSON Web Token (JWT) as the ID token. JWTs consist of three parts: a header, a payload, and a signature, all separated by dots.
Create the header
Headers have two fields:
kid
: The key id of the key used to sign the payloadalg
: The hashing algorithm being used, such as HMAC SHA256 or RSAHere’s an example:
1 2 3 4 | { kid: "r4p5SbL2qaFehFzhj8gI", alg: "RS256" } |
This JSON is then Base64Url encoded to form the first part of the JSON Web Token.
Create the payload
The payload contains claims - the metadata about the user.
Claim | Description |
---|---|
aud |
The audience identifier the ID token is intended for. Set this field to your app’s Client ID. Use the Client ID for the type of app you’re validating (i.e. an app in development or one that’s live and in production). |
auth_time |
The time the ID token was authorized |
exp |
The time the ID token expires. This is in unix time in integer seconds. |
iat |
The time the ID token was issued. This is in unix time in integer seconds. |
iss |
The identifier for the issuer of the response. Set this field to https://oauth.platform.intuit.com/op/v1. |
realmId |
The identifier for the specific QuickBooks Online company connected to your app. The realmID is returned whenever apps specify the QuickBooks Online API or Payments API scopes in authorization requests. |
sub |
The identifier for users’ Intuit accounts. This value is unique across all Intuit products. It’s never reused. Keep in mind, an Intuit account can have multiple emails at various points in time. However, the sub value never changes. Use the sub value as a unique identifier key for users. |
Here’s an example payload:
1 2 3 4 5 6 7 8 9 10 11 | { "sub": "1182d6ec-2a1f-4aa3-af3f-bb3b95db45af", "aud": [ "L3Y7SV6rRxVvArdYzlRxjPXo0b6ItrX4qFhopPXQ6aaEWgKyCa" ], "realmId": "123145880168382", "auth_time": 1464330769, "iss": "https://oauth.platform.intuit.com/op/v1", "exp": 1464335838, "iat": 1464332238 } |
Create the signature
Signatures verify the identity of the source sending the JSON Web Token.
To create the signature, sign a concatenation of the encoded header, the encoded payload, and a secret with a specified algorithm.
HMAC is a good standard. Here’s an example signature using the HMAC SHA256 algorithm:
1 | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
Use access tokens to call a user’s Intuit user profile endpoint and get additional data about them.
The response for this endpoint depends on the scopes you chose when you got the original access token. We reviewed scopes in Step 1.
Here’s an example response if you picked the openID, email, and profile scopes:
1 2 3 4 5 6 7 | { "sub": "1182d6ec-2a1f-4aa3-af3f-bb3b95db45af", "email": "john@doe.com", "emailVerified": true, "givenName": "John", "familyName": "Doe" } |
Warning
Here are example requests for supported SDKs:
.NET
Java
PHP
Node.js
Python
Ruby
1 | var userInfoResp = await auth2Client.GetUserInfoAsync(“accessToken‘); |
1 2 3 4 5 | //Prepare OAuth2PlatformClient OAuth2PlatformClient client = new OAuth2PlatformClient(oauth2Config); //Get user info (Use access token from bearerTokenResponse) UserInfoResponse response = client.getUserInfo(accessToken); |
1 | $userInfo = $OAuthLoginHelper::getUserInfo($accessToken);
|
1 2 3 4 5 6 7 | oauthClient.getUserInfo() .then(function(response){ console.log('The User Info is : ' + JSON.stringify(response.json())); }) .catch(function(e) { console.log('The error is '+ JSON.stringify(e)); }); |
1 | response = auth_client.get_user_info(access_token='EnterAccessTokenHere') |
1 | result = oauth_client.openid.get_user_info('accessToken') |
Get the base URI from the discovery document. You can also follow these links:
Create a GET request and use to the userinfo_endpoint
. Here’s an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | GET https://accounts.platform.intuit.com/v1/openid_connect/userinfo Accept: application/json Authorization: Bearer <access token> { "sub": "1182d6ec-2a1f-4aa3-af3f-bb3b95db45af", "email": "john@doe.com", "emailVerified": true, "givenName": "John", "familyName": "Doe", "phoneNumber": "+1 6305555555", "phoneNumberVerified": false, "address": { "streetAddress": "2007 saint julien ct", "locality": "mountain view", "region": "CA", "postalCode": "94043", "country": "US" } } |
After getting a user’s info, query your app’s user database.
If the user already exists, but their Intuit user profile (identified by the sub
field) isn’t connected or established, initiate an application session for the user. Prompt them to enter their password and ask if you can want you to link their Intuit user profile info to their existing account on your database.
If the user doesn’t exist in your user database, redirect them to your app’s new user signup flow so they can create a new profile. You may be able to use info from our APIs to auto-register users, or populate data fields in your signup flow.
Follow the same basic steps as the OAuth 2.0 implementation to refresh or revoke access tokens.