Ambiguous Authentication in Netsuite Token Based API call
Asked Answered
A

8

8

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!

Aquiculture answered 4/7, 2017 at 2:44 Comment(1)
I know this is old but what was the answer to this question?Tinner
H
9

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

Haywire answered 18/10, 2018 at 15:10 Comment(1)
With Netsuite now requiring TBA for API authentication in March 2019, all Netsuite API developers will be forced to remove the ApplicationInfo from their authentication if they have not already.Viable
F
7

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

Flowing answered 5/7, 2017 at 1:59 Comment(3)
That's exactly it! Thanks! I was using this call to test, so I removed this token passport from this call and left it in on all other calls and it started working.Aquiculture
By removing the tokenPassport , I was able to successfully run the getDataCenter request alone. But if I tried the get call for a vendorBill that fails. can you please help ?Refinement
You have to have the passport for vendor bills. That's sensitive info that you must be authenticated for. The data center is "free" information.Flowing
T
6

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);
}
Thrombo answered 6/11, 2017 at 22:25 Comment(0)
C
2

I had to modify the XML and remove the tokenpassport(account,comsumer key,token,nonce,timestamp) tag and it worked.

Cry answered 11/7, 2017 at 23:25 Comment(0)
E
1

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>'
Euphrasy answered 4/7, 2017 at 8:25 Comment(4)
Thanks for your answer. Are you saying the way I'm generating the signature is wrong. How can you be sure. The error just says "Ambiguous authentication".Aquiculture
I can't be sure but it's worth a try as nothing else jumps out to me from your code.Euphrasy
It's been a while since I have gone through that but I remember getting that "Ambiguous authentication" error and one of the last things I tried that got rid of it was doing the Base64 at the end.Euphrasy
It looks like you're doing the same thing as me. Can't see a difference. ThanksAquiculture
H
1

Remove the passport. Sadly, NetSuite fails if you have this in the code when using Token Auth. :/

Holston answered 5/7, 2017 at 11:38 Comment(0)
H
0

As per Netsuite SuiteTalk SOAP API document:

Ambiguous Authentication Errors When you use token-based authentication (TBA) in SOAP 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 SOAP web services request contains a combination of the Passport, TokenPassport and SsoPassport complex types.

■ If a single SOAP web services request contains both the tokenPassport and the ApplicationInfo complex types, and therefore contains the application ID in the SOAP header.

Hunger answered 11/6, 2021 at 4:1 Comment(0)
N
0

Here is the full code, updated for NetSuite 2023_2 Web Services. (SOAP API). I updated to .NET 8 and took advantage of new features. Also refactored all of this auth code into its own service that is very easy to call like this:

    var authenticator = new TokenBasedAuthenticator();
    _tokenPassport = authenticator.GetTokenPassport(settings.TokenInfo);

TokenBasedAuthenticator

internal class TokenBasedAuthenticator : ITokenBasedAuthenticator
{
    public TokenPassport GetTokenPassport(TokenInfo tokenInfo)
    {
        ArgumentNullException.ThrowIfNull(tokenInfo, nameof(tokenInfo));
        ArgumentNullException.ThrowIfNull(tokenInfo.Account, nameof(tokenInfo.Account));
        ArgumentNullException.ThrowIfNull(tokenInfo.ConsumerKey, nameof(tokenInfo.ConsumerKey));
        ArgumentNullException.ThrowIfNull(tokenInfo.ConsumerSecret, nameof(tokenInfo.ConsumerSecret));
        ArgumentNullException.ThrowIfNull(tokenInfo.Token, nameof(tokenInfo.Token));
        ArgumentNullException.ThrowIfNull(tokenInfo.TokenSecret, nameof(tokenInfo.TokenSecret));

        var tokenPassport = new TokenPassport
        {
            account = tokenInfo.Account,
            consumerKey = tokenInfo.ConsumerKey,
            token = tokenInfo.Token,
            nonce = ComputeNonce(),
            timestamp = ComputeTimestamp(),
        };

        var keyBytes = GetSecretBytes(tokenInfo);
        var baseStringBytes = GetIdentificationBytes(tokenPassport);

        using var hmac = new HMACSHA256(keyBytes);
        var hashBaseString = hmac.ComputeHash(baseStringBytes);

        tokenPassport.signature = new TokenPassportSignature
        {
            algorithm = "HMAC-SHA256",
            Value = Convert.ToBase64String(hashBaseString),
        };

        return tokenPassport;
    }

    private static byte[] GetIdentificationBytes(TokenPassport tokenPassport)
    {
        var baseString = tokenPassport.account
                         + "&" + tokenPassport.consumerKey
                         + "&" + tokenPassport.token
                         + "&" + tokenPassport.nonce
                         + "&" + tokenPassport.timestamp;
        var encoding = new ASCIIEncoding();
        return encoding.GetBytes(baseString);
    }

    private static byte[] GetSecretBytes(TokenInfo tokenInfo)
    {
        var key = tokenInfo.ConsumerSecret + "&" + tokenInfo.TokenSecret;
        var encoding = new ASCIIEncoding();
        return encoding.GetBytes(key);
    }

    private string ComputeNonce()
    {
        var randomNumberGenerator = RandomNumberGenerator.Create();
        var data = new byte[20];
        randomNumberGenerator.GetBytes(data);
        var value = Math.Abs(BitConverter.ToInt32(data, 0));
        return value.ToString();
    }

    private long ComputeTimestamp()
    {
        return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
    }
}

TokenInfo

public class TokenInfo
{
    public string? Account { get; set; }
    public string? ConsumerKey { get; set; }
    public string? ConsumerSecret { get; set; }
    public string? Token { get; set; }
    public string? TokenSecret { get; set; }
}

This is a nice object you can load from appsettings.json.

Nuncio answered 19/2 at 14:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.