I am following the 3 step authorization flow using PHP/Laravel. I have been able to pass step one and two which is getting the unauthorized token and redirecting the user to the netsuite login form. After the redirecting the user, I am able to login, put my 2FA from my authenticator app. After that it tells me to ‘allow’ or ‘deny’ the application, I click on allow. After clicking on allow I get a ‘USER_ERROR’ ‘Invalid attempt to login’. I also get an InvalidSignature error in my Audit trail. below is the code I am using.
Basically this is my company’s Netsuite account in production. I created two users, an admin and a user which just has a TBA role for testing the 3 step authorization flow. So for testing purposes, I am trying to log into the account using token based authentication. I created an integration, I also created an Access Token assigned to the user and assigned to the created integration. what could be the issue and how can I resolve the InvalidSignature error. Please any help will be appreciated.
class NetSuiteIntegrationHelper
{
protected $config;
protected static $lastTimestamp;
public function __construct()
{
$this->config = [
'consumer_key' => env('NETSUITE_CONSUMER_KEY'),
'consumer_secret' => env('NETSUITE_CONSUMER_SECRET'),
'token_secret' => env('NETSUITE_TOKEN_SECRET'),
'token_id' => env('NETSUITE_TOKEN_ID'),
];
if (! $this->config['consumer_key'] || ! $this->config['consumer_secret']) {
throw new Exception('NetSuite consumer key or secret is not set in .env file');
}
}
private function getCurrentTimestamp()
{
$pacificTime = new DateTime('now', new DateTimeZone('America/Los_Angeles'));
$currentTimestamp = $pacificTime->getTimestamp();
if ($currentTimestamp <= self::$lastTimestamp) {
$currentTimestamp = self::$lastTimestamp + 1;
}
self::$lastTimestamp = $currentTimestamp;
return $currentTimestamp;
}
public function getUnauthorizedRequestToken($callbackUrl, $accountId, $role = null)
{
$params = [
'oauth_callback' => $callbackUrl,
'oauth_consumer_key' => $this->config['consumer_key'],
'oauth_nonce' => $this->generateNonce(),
'oauth_signature_method' => 'HMAC-SHA256',
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
];
$baseString = $this->buildBaseString('POST', "https://{$accountId}.restlets.api.netsuite.com/rest/requesttoken", $params);
$compositeKey = $this->config['consumer_secret'].'&';
$params['oauth_signature'] = base64_encode(hash_hmac('sha256', $baseString, $compositeKey, true));
$header = 'OAuth '.$this->buildAuthorizationHeader($params);
$response = Http::withHeaders(['Authorization' => $header])
->post("https://{$accountId}.restlets.api.netsuite.com/rest/requesttoken");
if ($response->failed()) {
dump($response);
return ['error' => $response->body()];
}
return $this->parseResponse($response->body());
}
public function getAccessToken($token, $verifier, $accountId)
{
$params = [
'oauth_token' => $token,
'oauth_verifier' => $verifier,
'oauth_consumer_key' => $this->config['consumer_key'],
'oauth_nonce' => $this->generateNonce(),
'oauth_signature_method' => 'HMAC-SHA256',
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
'realm' => $accountId,
];
$baseString = $this->buildBaseString('POST', "https://{$accountId}.restlets.api.netsuite.com/rest/accesstoken", $params);
$compositeKey = rawurlencode($this->config['consumer_secret']).'&'.rawurlencode($this->config['token_secret']);
$signedBasedString = hash_hmac('sha256', $baseString, $compositeKey, true);
$signature = base64_encode($signedBasedString);
$params['oauth_signature'] = $signature;
$header = 'OAuth '.$this->buildAuthorizationHeader($params);
$response = Http::withHeaders(['Authorization' => $header])
->post("https://{$accountId}.restlets.api.netsuite.com/rest/accesstoken");
if ($response->failed()) {
dump($response, $this->config['consumer_secret'], $this->config['token_secret']);
return ['error' => $response->body()];
}
return $this->parseResponse($response->body());
}
private function buildBaseString($method, $url, $params)
{
if (isset($params['realm'])) {
unset($params['realm']);
}
ksort($params);
$paramString = [];
foreach ($params as $key => $value) {
$paramString[] = $key.'='.rawurlencode($value);
}
$baseString = strtoupper($method).'&'.rawurlencode($url).'&'.rawurlencode(implode('&', $paramString));
return $baseString;
}
private function buildAuthorizationHeader($params)
{
$header = [];
ksort($params);
foreach ($params as $key => $value) {
$header[] = rawurlencode($key).'="'.rawurlencode($value).'"';
}
return implode(', ', $header);
}
private function generateNonce()
{
return Str::random(20);
}
private function parseResponse($response)
{
parse_str($response, $parsed);
return $parsed;
}
}
I am trying to authenticate the user from my app to netsuite using the 3 step authorization flow. After clicking on allow to authenticate the app, I get an Invalid Attempt to Login ‘USER_ERROR’. what I expect is for the post request to return the access token and secret object.