I am trying to make SOAP calls to the Netsuite API using Token Based Authentication. I have a C# client that is generated from WDSL and it is sending the following request (with the secrets replaced).
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:messages_2016_2.platform.webservices.netsuite.com" xmlns:urn1="urn:core_2016_2.platform.webservices.netsuite.com">
<soapenv:Header>
<urn:partnerInfo>
<urn:partnerId>[MyAccountId]</urn:partnerId>
</urn:partnerInfo>
<urn:applicationInfo>
<urn:applicationId>[MyApplicationId]</urn:applicationId>
</urn:applicationInfo>
<urn:tokenPassport>
<urn1:account>[MyAccountId]</urn1:account>
<urn1:consumerKey>[MyConsumerKey]</urn1:consumerKey>
<urn1:token>[MyTokenId]</urn1:token>
<urn1:nonce>1574515852</urn1:nonce>
<urn1:timestamp>1499135589</urn1:timestamp>
<urn1:signature algorithm="HMAC-SHA1">Ll8DbLvTWsBh/G7UtenErR03OrM=</urn1:signature>
</urn:tokenPassport>
</soapenv:Header>
<soapenv:Body>
<urn:getDataCenterUrls>
<urn:account>[MyAccountId]</urn:account>
</urn:getDataCenterUrls>
</soapenv:Body>
</soapenv:Envelope>
I am getting the following response
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Server.userException</faultcode>
<faultstring>Ambiguous authentication</faultstring>
<detail>
<platformFaults:invalidCredentialsFault xmlns:platformFaults="urn:faults_2016_2.platform.webservices.netsuite.com">
<platformFaults:code>USER_ERROR</platformFaults:code>
<platformFaults:message>Ambiguous authentication</platformFaults:message>
</platformFaults:invalidCredentialsFault>
<ns1:hostname xmlns:ns1="http://xml.apache.org/axis/">partners-java20004.sea.netledger.com</ns1:hostname>
</detail>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
I have tried lots of different ways of generating the signature, nonce and timestamp. Currently I have the following:
private string computeNonce()
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] data = new byte[20];
rng.GetBytes(data);
int value = Math.Abs(BitConverter.ToInt32(data, 0));
return value.ToString();
}
private long computeTimestamp()
{
return ((long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
}
private TokenPassportSignature computeSignature(string accountId, string consumerKey, string consumerSecret, string tokenId, string tokenSecret, string nonce, long timestamp)
{
string baseString = accountId + "&" + consumerKey + "&" + tokenId + "&" + nonce + "&" + timestamp;
string key = consumerSecret + "&" + tokenSecret;
string signature = "";
var encoding = new System.Text.ASCIIEncoding();
byte[] keyBytes = encoding.GetBytes(key);
byte[] baseStringBytes = encoding.GetBytes(baseString);
using (var hmacSha1 = new HMACSHA1(keyBytes))
{
byte[] hashBaseString = hmacSha1.ComputeHash(baseStringBytes);
signature = Convert.ToBase64String(hashBaseString);
}
TokenPassportSignature sign = new TokenPassportSignature();
sign.algorithm = "HMAC-SHA1";
sign.Value = signature;
return sign;
}
Does anyone have any ideas? Thanks!
Token based authentication is a per user authentication and requires certain permissions in NetSuite. An existing role can be used (recommended) or a new role can be created. The relevant role permissions are under the 'Setup' subtab the following details were gleaned from SuiteAnswer(41898). Access Token Management.
Click Setup > Users/Roles > Access Tokens > New.Select the User that you created in the previous step. Select the role that you created in Step 2. Click Save. The Token ID / Secret are displayed.
I was also struggling with this unhelpful error after switching to TBA. Turns out I was still sending the ApplicationInfo
property along with the new Consumer Key
and Consumer Secret
.
I found this on NetSuite's "SuiteAnswers" site and wanted to quote it here for anyone else still having this problem.
Ambiguous Authentication Errors
When you use token-based authentication (TBA) in web services, an ambiguous authentication error response is returned if you use another authentication mechanism together with the TBA header.
You receive this error if besides the TBA header, your request contains an Application ID, a passport object with an email address and a password, or a valid JSESSIONID.
The error occurs in the following cases:
If a single web services request contains a combination of the Passport, TokenPassport and SsoPassport complex types.
If a single web services request contains both the tokenPassport and the ApplicationInfo complex types, and therefore contains the application ID in the SOAP header.
Source: Token-Based Authentication Errors in Web Services
The getDataCenter calls don't need the passport. I just had the same issue with the mapSso function. It looks like the 2017.1 release made them stricter on not accepting the passport
I had to modify the XML and remove the tokenpassport(account,comsumer key,token,nonce,timestamp) tag and it worked.
I know this is an old question, but I struggled with the same issue, and found a working solution.
private static void CreateTokenPassport()
{
// Initialize the netsuite web service proxy.
_netSuiteService = new NetSuiteService();
// A valid Token passport signature consists of the following:
// Create a base string.
// The base string is variable created from concatenating a series of values specific to the request.Use an ampersand as a delimiter between values.
// The values should be arranged in the following sequence:
// NetSuite account ID
// Consumer key
// Token
// Nonce(a unique, randomly generated alphanumeric string, with a minimum of six characters and maximum of 64)
// Timestamp
// See: https://system.na1.netsuite.com/app/help/helpcenter.nl?fid=section_4395630653.html#bridgehead_4398049137
string consumerSecret = "";
string tokenSecret = "";
string accountId = "";
string consumerKey = "";
string tokenId = "";
string nonce = ComputeNonce();
long timestamp = ComputeTimestamp();
string baseString = string.Format("{0}&{1}&{2}&{3}&{4}", accountId, consumerKey, tokenId, nonce, timestamp);
string secretKey = string.Format("{0}&{1}", consumerSecret, tokenSecret);
// Initialize the keyed hash object using the secret key as the key
HMACSHA256 hashObject = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
// Computes the signature by hashing the data with the secret key as the key
byte[] signature = hashObject.ComputeHash(Encoding.UTF8.GetBytes(baseString));
// Base 64 Encode
string encodedSignature = Convert.ToBase64String(signature);
TokenPassport tokenPassport = new TokenPassport
{
signature = new TokenPassportSignature
{
Value = encodedSignature,
algorithm = "HMAC_SHA256"
},
account = accountId,
consumerKey = consumerKey,
token = tokenId,
nonce = nonce,
timestamp = timestamp
};
_netSuiteService = new NetSuiteService
{
tokenPassport = tokenPassport
};
}
Utility Methods:
private static string ComputeNonce()
{
return Guid.NewGuid().ToString("N");
}
private static long ComputeTimestamp()
{
return ((long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
}
I don't know how it's done in C# with HMAC-SHA1 but in Javascript with CryptoJS HMAC-SHA256 you first sign the string and then encode it in Base64:
var baseString = ACCOUNT_ID + "&" + NETSUITE_CONSUMER_KEY + "&" + NETSUITE_TOKEN_ID + "&" + NONCE + "&" + TIMESTAMP;
var key = NETSUITE_CONSUMER_SECRET + '&' + NETSUITE_TOKEN_SECRET;
var HMAC256_Sig = cryptoJS.HmacSHA256(baseString, key);
var HMAC256_Sig_Base64 = cryptoJS.enc.Base64.stringify(HMAC256_Sig);
Then you output it like:
'<platformCore:signature algorithm = "HMAC_SHA256">' + HMAC256_Sig_Base64 + '</platformCore:signature>'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With