NetSuite offers various methods for authenticating and integrating with its system. Among these are:
In this article we are going to focus on the Three-step authorization flow for setting up Token-based Authentication (TBA) in a Laravel application.
First, we enable Token Based Authentication (TBA) feature in our NetSuite Account.
Setup
> Company
> Enable Features
.
After successfully enabling the TBA feature, we can then proceed to create an integration if you do not have an existing integration. Navigate to: Setup
> Integration
> Manage Integrations
> New
Token-Based Authentication
and TBA: Authorization Flow
under the Authentication
tab.Consumer ID
and Consumer Secret
.
When you click the save button, do not immediately leave the page because the Consumer ID
and Consumer Secret
will be displayed under the client credentials tab after you click on save. Copy these values and store somewhere safe as we will be needing it when during integration in our application.
After successfully creating an integration, next we need to create a role that would use the token based authentication feature.
Setup
> Users/Roles
> Manage Roles
> New
.Permissions
tab, click on the Reports
sub tab, add the SuiteAnalytics Workbook
permission set the access level to Edit
.Permissions
tab, click on the Setup
sub tab, add the following permissions and set the access level to full
After creating the TBA role, assign a user to the TBA role you just created.
Setup
> Users/Roles
> Manage Users
.
Access
tab, ensure that the user has access. if the user doesn't, click on the give access checkbox to give the user access and also enter a default password for the user.Roles
sub tab of the Access
tab, look for the TBA role we created in previous steps in the select dropdown, add the role to the user.
After assigning the TBA role we just created to the user, we can now start our implementation in Laravel using the following credentials we created in previous steps.
We can do this in many ways, but our approach here would be to allow different users connect there different NetSuite accounts to our platform as long as they have the TBA role assigned to that user using the credentials obtained (Account ID
, Client ID
, and Client Secret
).
To set up this authentication flow in your Laravel application, create a form to collect these credentials and configure routes and controllers to handle the authentication flow.
Create a form that would collect the credentials we got from previous steps.
First, create the callback URL route to handle the response NetSuite returns when the connection is successful. Ensure this callback URL matches the one you specified during the integration setup in NetSuite.
Route::get('/netsuite/callback', [NetSuiteController::class, 'handleCallback'])->name('netsuite.callback');
Next we need to make a POST request to obtain an unauthorized request token from NetSuite. This request returns an oauth_token
and oauth_token_secret
. We will save the oauth_token_secret
to use it a later request while we will use the oauth_token in the next request to redirect the user to NetSuite UI login page using the code below.
public function getRequestToken($formData)
{
// Get details from the form
$callback = route('netsuite.callback'); // Define the callback URL
$accountId = $formData['account_id']; // NetSuite account ID
$consumerId = $formData['consumer_id']; // Consumer ID from NetSuite
$consumerSecret = $formData['consumer_secret']; // Consumer secret from NetSuite
// Structure the parameters that will be passed to the request
$params = [
'oauth_callback' => $callback, // Callback URL
'oauth_consumer_key' => $consumerId, // Consumer key (ID)
'oauth_nonce' => Str::random(32), // Unique nonce for this request
'oauth_signature_method' => 'HMAC-SHA256', // Signature method
'oauth_timestamp' => time(), // Current timestamp
'oauth_version' => '1.0', // OAuth version
];
// Sort parameters by key
ksort($params);
// Encode each key-value pair and join them with '&'
$paramString = [];
foreach ($params as $key => $value) {
$paramString[] = $key . '=' . rawurlencode($value);
}
// Create the base string by combining HTTP method, URL, and parameters
$baseString = strtoupper('POST') . '&' . rawurlencode("https://{$accountId}.restlets.api.netsuite.com/rest/requesttoken") . '&' . rawurlencode(implode('&', $paramString));
// Create the composite key for HMAC-SHA256 hashing
$compositeKey = $consumerSecret . '&';
// Generate the OAuth signature and add it to the parameters
$params['oauth_signature'] = base64_encode(hash_hmac('sha256', $baseString, $compositeKey, true));
// Build the Authorization header for the OAuth request
$header = [];
// Sort parameters by key
ksort($params);
// Encode each key-value pair and format them for the header
foreach ($params as $key => $value) {
$header[] = $key . '="' . rawurlencode($value) . '"';
}
// Combine all parts of the header
$authorizationHeader = 'OAuth ' . implode(', ', $header);
try {
// Send the request to NetSuite to get the request token
$response = Http::withHeaders(['Authorization' => $authorizationHeader])
->post("https://{$accountId}.restlets.api.netsuite.com/rest/requesttoken");
// Handle failed response
if ($response->failed()) {
$responseBody = $response->body();
$decodedResponse = json_decode($responseBody, true) ?? [];
return [ 'error' => $decodedResponse['error']['message'] ];
}
// Get the response
$responseBody = $response->body();
// Parse the response into an array
parse_str($responseBody, $parsedResponse);
// Extract necessary data from the response and form
$oauthToken = $parsedResponse['oauth_token'];
$oauthTokenSecret = $parsedResponse['oauth_token_secret'];
// We are storing the data in session because we will need it to handle the callback when the user is redirect back. We will clear the session data when handling the callback from NetSuite
session([
'oauth_token' => $oauthToken,
'oauth_token_secret' => $oauthTokenSecret,
'account_id' => $accountId,
'consumer_id' => $consumerId,
'consumer_secret' => $consumerSecret,
]);
// Redirect the user to NetSuite for authorization
return redirect("https://{$accountId}.app.netsuite.com/app/login/secure/authorizetoken.nl?oauth_token={$oauthToken}");
} catch (\Exception $e) {
// Handle exceptions and return an error message
return ['error' => $e->getMessage()];
}
}
After we get the Unauthorized Request Token from NetSuite, we redirect the user to NetSuite login UI to login and authorize the token.
The user might be required to connect an authenticator app for 2FA.
Upon entering the verification code and successfully authenticating, the user will be prompted to grant or deny access to the application attempting to connect to NetSuite. By clicking Allow
, the user will be redirected to the callback URL specified during the NetSuite integration setup. This callback URL corresponds to the /netsuite/callback
route we defined in earlier.
When NetSuite redirects the user to the callback URL, it indicates that the connection was successful. At this point, our application needs to handle this callback in our NetSuiteController
. This involves making a follow-up request to NetSuite to obtain the access tokens. These access tokens, specifically the oauth_token
and oauth_token_secret
, are dynamically generated by NetSuite and are essential for authenticating subsequent API requests to NetSuite on behalf of the user. Using the code below we can handle the callback and make another POST request to NetSuite to retrieve the access tokens.
public function handleCallback(Request $request)
{
// Retrieve the tokens we saved in the session in the first step
$sessionOauthToken = session('oauth_token');
$sessionOauthTokenSecret = session('oauth_token_secret');
$sessionAccountId = session('account_id');
$sessionConsumerId = session('consumer_id');
$sessionConsumerSecret = session('consumer_secret');
// Retrieve the query params from the callback request which includes the account Id as company, oauth_verifier and new oauth_token that should match the oauth_token we saved in session for the firs step.
$token = $request->query('oauth_token');
$oauthVerifier = $request->query('oauth_verifier');
$accountId = $request->query('company');
// The oauth token from step one should match the oauth token from step two same as the accountId we sent from the form should match the company query param returned
if ($sessionOauthToken != $token || $sessionAccountId !== $accountId) {
return 'Invalid Request';
}
$params = [
'oauth_token' => $token,
'oauth_verifier' => $oauthVerifier,
'oauth_consumer_key' => $sessionConsumerId,
'oauth_nonce' => Str::random(32),
'oauth_signature_method' => 'HMAC-SHA256',
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
];
// Sort parameters by key
ksort($params);
// Encode each key-value pair and join them with '&'
$paramString = [];
foreach ($params as $key => $value) {
$paramString[] = $key.'='.rawurlencode($value);
}
// Create the base string by combining HTTP method, URL, and parameters
$baseString = strtoupper('POST').'&'.rawurlencode("https://{$accountId}.restlets.api.netsuite.com/rest/accesstoken").'&'.rawurlencode(implode('&', $paramString));
// Create the composite key for HMAC-SHA256 hashing
$compositeKey = $sessionConsumerSecret.'&'.$sessionOauthTokenSecret;
$signedBasedString = hash_hmac('sha256', $baseString, $compositeKey, true);
$signature = base64_encode($signedBasedString);
// Generate the OAuth signature and add it to the parameters
$params['oauth_signature'] = $signature;
// We need the realm to build the header but we do not need it to create the base string
$params['realm'] = $accountId;
// Build the Authorization header for the OAuth request
$header = [];
// Sort parameters by key
ksort($params);
// Encode each key-value pair and format them for the header
foreach ($params as $key => $value) {
$header[] = $key.'="'.rawurlencode($value).'"';
}
// Combine all parts of the header
$authorizationHeader = 'OAuth '.implode(', ', $header);
try {
// Send the request to NetSuite to get the access tokens
$response = Http::withHeaders(['Authorization' => $authorizationHeader])
->post("https://{$accountId}.restlets.api.netsuite.com/rest/accesstoken");
// Handle failed response
if ($response->failed()) {
$responseBody = $response->body();
$decodedResponse = json_decode($responseBody, true) ?? [];
return ['error' => $decodedResponse['error']['message']];
}
// Get the response
$responseBody = $response->body();
// Parse the response into an array
parse_str($responseBody, $parsedResponse);
// Extract the access tokens the response, at this point we have handled the callback and retrieved the access tokens. From here you should save the access tokens somewhere safe and retreievable you will be using these tokens to be subsequent requests to pull in data from NetSuite to your application.
$oauthToken = $parsedResponse['oauth_token'];
$oauthTokenSecret = $parsedResponse['oauth_token_secret'];
// Dump the response to see the data returned
dd($parsedResponse);
// Since we have successfully retrieved the tokens, we can now remove this values from the session as it is no longer needed.
session()->forget([
'oauth_token',
'oauth_token_secret',
'account_id',
'consumer_id',
'consumer_secret',
]);
// Redirect the user to the appropriate page.
return redirect('redirect user to appropriate page after successful login and access token retrieval')
} catch (\Exception $e) {
// Handle exceptions and return an error message
return ['error' => $e->getMessage()];
}
}
After handling the callback and obtaining the access tokens, you can make authenticated requests to the NetSuite API using these tokens. Since the tokens do not expire, it is advisable to store them securely in your database. Re-authenticating will generate new tokens, so saving the tokens helps avoid unnecessary duplications. You can confirm the successful creation of access tokens by visiting the "Manage Access Tokens" page in NetSuite. By saving and reusing these tokens, you can maintain a stable and efficient connection with NetSuite, allowing your application to seamlessly access and manipulate data as needed. This approach leverages the security and convenience of OAuth 1.0 while providing the robustness required for enterprise-level integrations. Happy Coding!