DEV Community

Otutu Chinedu Kingsley
Otutu Chinedu Kingsley

Posted on

How to set up NetSuite Token-based Authentication (TBA) in Laravel

NetSuite offers various methods for authenticating and integrating with its system. Among these are:

  • Token-based Authentication (TBA)
  • OAuth 2.0

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.

Enable Features

First, we enable Token Based Authentication (TBA) feature in our NetSuite Account.

  • Log into your NetSuite account
  • Navigate to: Setup > Company > Enable Features.

A picture of how to navigate to the page in NetSuite UI

  • Under the Analytics tab, check SuiteAnalytics Workbook.

A picture of the option to check in NetSuite UI

  • Under the SuiteCloud tab, check all options for SuiteScript.

A picture of the option in NetSuite UI

  • Under the SuiteCloud tab, check all options for SuiteTalk (Web Services).

A picture of the option in NetSuite UI

  • Under the  SuiteCloud tab, Manage Authentication sub tab check Token-based Authentication.

A picture of the option in NetSuite UI

  • Save your changes

Create an Integration

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

A picture of the option in NetSuite UI

  • Fill out the form, selecting only Token-Based Authentication and TBA: Authorization Flow under the Authentication tab.

  • Add a callback URL that matches the URL in your application that will handle NetSuite redirection during the authentication process.

  • Save the integration and note down the 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.

Create a TBA Role

After successfully creating an integration, next we need to create a role that would use the token based authentication feature.

  • Navigate to: Setup > Users/Roles > Manage Roles > New.

  • Fill in the name and leave the authentication fields blank.

  • In the Permissions tab, click on the Reports sub tab, add the SuiteAnalytics Workbook permission set the access level to Edit.

  • In the Permissions tab, click on the Setup sub tab, add the following permissions and set the access level to full

    • Log in using access tokens
    • REST web services
    • SOAP web services
    • User Access Tokens
  • Save the changes.

Assign User to Role

After creating the TBA role, assign a user to the TBA role you just created.

  • Navigate to: Setup > Users/Roles > Manage Users.

A picture of the option in NetSuite UI

  • In the list of users that is displayed click on the users name, then click on edit to edit the user details.

  • Go to the 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.

  • In the 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.

  • Save the changes.

A picture of the option in NetSuite UI

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.

  • Account ID - NetSuite's account ID is crucial for authentication,
    and you can find it in your NetSuite URL. For example, in the URL
    https://12345.app.netsuite.com, the number 12345 is your account
    ID.

  • Client ID (also known as Consumer Key) - We got this when we
    created the integration.

  • Client Secret (also known as Consumer Secret) - We got this when
    we created the integration.

Using Laravel to Authenticate NetSuite REST API

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 Form

Create a form that would collect the credentials we got from previous steps.

A picture of the option in NetSuite UI

Integrating NetSuite API

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');
Enter fullscreen mode Exit fullscreen mode

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()];
    }
}
Enter fullscreen mode Exit fullscreen mode

After we get the Unauthorized Request Token from NetSuite, we redirect the user to NetSuite login UI to login and authorize the token.

A picture of the option in NetSuite UI

The user might be required to connect an authenticator app for 2FA.

A picture of the option in NetSuite UI

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.

A picture of the option in NetSuite UI

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()];
        }
    }
Enter fullscreen mode Exit fullscreen mode

Conclusion

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!

Top comments (0)