Antony Petras

NetSuite - Access RESTlets with OAuth 1.0 in C#

If you’ve found your way here, you are probably close to losing hope. Do not fear, because the answer to all your problems lies within.

In the 3 years this post has been up, I have received many comments telling me how useful it is and how people finally got their solutions working with my help. At the time of original writing, OAuth 1.0 (SHA1 based) was basically your only option if you wanted to access RESTlets using an external application. Now with NetSuite supporting OAuth 2.0, OAuth 1.0 is old and dirty, and really should not be deployed for use in new projects, especially not those using modern application frameworks e.g .NET Core. At least, that is what you would assume…

Nope! While using OAuth 2.0 would be the “right” thing to do, and that is certainly what I try to do in the first instance, it is nothing short of a soul-draining nightmare to get this set up with NetSuite. Not because OAuth 2.0 is hard - certainly not, OAuth 2.0 and OpenID are even pleasant to work with outside of a NetSuite context. But, as usual, NetSuite’s implementation is… rough. As I typically work on integration / automated applications, the flavour of OAuth 2.0 I am talking about here is the Client Credentials Flow. I can’t comment on the ease of setup of the other authorization flows, though they do appear to be a bit simpler. So, despite it being out of date, OAuth 1.0 still works and is much more straightforward to implement.

So instead of retiring this post, I have cleaned it up to more modern standards. Enjoy!

If you want to implement OAuth 2.0 instead - see my post on that here.

Create the Integration record

In NetSuite, head to Setup > Integration > Manage Integrations > New.
Just set a name for your integration, and ensure State = Enabled, and Token-Based Authentication is checked.

After clicking save, your Client ID and Client Secret will be displayed at the bottom of the screen. Copy them! You will never see them again.

Create the Access Token record

In NetSuite, head to Setup > Users/Roles > Access Tokens > New.
Select your integration record, and the User and Role that the integration will use.

After clicking save, your Token ID and Token Secret will be displayed at the bottom of the screen. Copy them! You will never see them again.

Other Information you will need

  • Realm – You can find it under Setup > Integration > Web Services Preferences, where it is labelled Account ID.
  • The URL of your RESTlet

The OAuthBase class

You’ll be needing this class. It generates your signature for you. I originally found this on the now-deleted MSDN forums. I reworked it to remove some of the manual parts, and now this may not work for non-NetSuite applications. Let me know in the comments how you go if you try.

OAuthBase.cs

It is a static class with a single method that we care about:

1
public static string GenerateAuthorizationHeaderValue(Uri uri, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string realm)

Generating an authorization header

Time to generate the header. If you want to learn the full details of this process, this article is really good.

All we have to do is feed everything into the GenerateAuthorizationHeaderValue method.

1
2
3
Uri uri = new Uri("https://[accountId].restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=[scriptId]&deploy=[deployId]")

string header = OAuthBase.GenerateAuthorizationHeaderValue(uri, consumerKey, consumerSecret, token, tokenSecret, httpMethod, realm);

Making an HTTP request

Then all you have to do is provide your header value as the Authorization header when you make the request.

In modern .NET you would create an HttpRequestMessage. You may use a different technique if you are working with older systems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var message = new HttpRequestMessage
{
RequestUri = uri,
Method = "GET",
Headers =
{
{ "Authorization", header }
}
}

// Don't actually create HTTP Clients like this, you should inject an IHttpClientFactory instead. See next section.
var client = new HttpClient();

var response = await client.SendAsync(message);

Techniques for Modern ASP.NET

If you’re designing a brand new application, you may want to consider an architecture similar to this:

  • A NetsuiteService to make requests to NetSuite
  • An OAuthService to create OAuth-compatible HttpRequestMessage objects
  • A DelegatingHandler to automatically apply OAuth 1.0 signatures to any request made to NetSuite.

NetsuiteOptions

Set up a NetsuiteOptions class to hold all of the NetSuite connection settings:

NetsuiteOptions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace MyApp
{
public class NetsuiteOptions
{
public string AccountId { get; set; }

public string ClientId { get; set; }
public string ClientSecret { get; set; }

public string TokenId { get; set; }
public string TokenSecret { get; set; }

public string Realm { get; set; }

public string ScriptId { get; set; }
public string DeploymentId { get; set; }
}
}

This is how my appsettings.json looks:

appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"Netsuite":
{
"AccountId": "1234567-sb1",
"ClientId": "xxxx",
"ClientSecret": "xxxx",
"TokenId": "xxxx",
"TokenSecret": "xxxx",
"Realm": "1234567_SB1",
"ScriptId": "1234",
"DeploymentId": "1"
}
}

NetsuiteService

NetsuiteService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

namespace MyApp
{
public interface INetsuiteService
{
public Task<TResponse> GetAsync<TResponse>(IEnumerable<KeyValuePair<string, string>> args = null);

public Task<TResponse> PostAsync<TResponse, TRequest>(TRequest body);
}

public class NetsuiteService : INetsuiteService
{
private readonly IHttpClientFactory _http;
private readonly NetsuiteOptions _options;

public NetsuiteService(IHttpClientFactory http, NetsuiteOptions options)
{
_http = http;
_options = options;
}

public async Task<TResponse> GetAsync<TResponse>(IEnumerable<KeyValuePair<string, string>> args = null)
{
var client = _http.CreateClient("netsuite");

var url = "https://" + _options.AccountId + ".restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=" + _options.ScriptId + "&deploy=" + _options.DeploymentId;

if (args != null)
{
foreach (var query in args)
{
url += "&" + query.Key + "=" + query.Value;
}
}

var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Get
};

var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();

return JsonConvert.DeserializeObject<TResponse>(content);
}

public async Task<TResponse> PostAsync<TResponse, TRequest>(TRequest body)
{
var client = _http.CreateClient("netsuite");

var request = new HttpRequestMessage
{
RequestUri = new Uri("https://" + _options.AccountId + ".restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=" + _options.ScriptId + "&deploy=" + _options.DeploymentId),
Method = HttpMethod.Post,
Content = JsonContent.Create(body)
};

var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();

return JsonConvert.DeserializeObject<TResponse>(content);
}
}
}

Notes on the above:

  • Get and Post are generic methods so we can specify the input and output classes at the call site
  • Injected are a NetsuiteOptions and an IHttpClientFactory
  • On lines 30 and 56 we are creating named HttpClients. This allows us to pull in previously-defined custom behaviour based on the name alone

OAuthService

OAuthService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Net.Http;

namespace MyApp
{
public class OAuthService
{

public OAuthService()
{
}

public HttpRequestMessage SignRequest(HttpRequestMessage message, string consumerKey, string consumerSecret, string tokenKey, string tokenSecret, string realm)
{
string header = OAuthBase.GenerateAuthorizationHeaderValue(message.RequestUri, consumerKey, consumerSecret, tokenKey, tokenSecret, message.Method.Method, realm);

return new HttpRequestMessage
{
RequestUri = message.RequestUri,
Method = message.Method,
Headers =
{
{ "Authorization", header }
},
Content = message.Content
};
}
}
}

Notes on the above:

  • SignRequest accepts an HttpRequestMessage and returns an HttpRequestMessage that has a valid OAuth 1.0 Authorization header
  • In its current state it could be a static class instead of a service, but this is more flexible in case it needs further dependencies
  • The headers from the original message are replaced / lost. You should adapt this if you care about the original headers

DelegatingHandler

NetsuiteDelegatingHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace MyApp
{
public class NetsuiteDelegatingHandler : DelegatingHandler
{
private readonly OAuthService _oauth;
private readonly NetsuiteOptions _netsuiteOptions;

public NetsuiteDelegatingHandler(NetsuiteOptions netsuiteOptions, OAuthService oauth)
{
_oauth = oauth;
_netsuiteOptions = netsuiteOptions;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
var req = _oauth.SignRequest(request, _netsuiteOptions.ClientId, _netsuiteOptions.ClientSecret, _netsuiteOptions.TokenId, _netsuiteOptions.TokenSecret, _netsuiteOptions.Realm);

return await base.SendAsync(req, cancellationToken);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.ToString());
throw ex;
}
}
}
}

Notes on the above:

  • We inherit from the default DelegatingHandler (basically what an HttpClient is)
  • We override the SendAsync method to first turn the request into a signed request, and then pass it along to the standard SendAsync method
  • We log any exceptions manually because .NET suppresses them here

Wiring it all up

You’ll need to add all of the below to the ConfigureServices method in Startup.cs:

Startup.cs
1
2
services.Configure<NetsuiteOptions>(Configuration.GetSection("Netsuite"));
services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<NetsuiteOptions>>().Value);
Startup.cs
1
services.AddHttpClient();
  • AddHttpClient() adds the default IHttpClientFactory to the service collection, plus dependencies
Startup.cs
1
services.AddTransient<OAuthService>();
  • AddTransient<OAuthService>() adds our OAuthService to the service collection
Startup.cs
1
services.AddTransient<INetsuiteService, NetsuiteService>();
  • AddTransient<INetsuiteService, NetsuiteService>() adds our NetsuiteService to the service collection
Startup.cs
1
2
3
4
5
6
7
services.AddHttpClient("netsuite")
.AddHttpMessageHandler((s) =>
{
return new NetsuiteDelegatingHandler(
s.GetService<NetsuiteOptions>(),
s.GetService<OAuthService>());
})
  • AddHttpClient(“netsuite”) adds a named HttpClient to the service collection. When we use _httpClientFactory.CreateClient(“netsuite”) in our services, it will use the configuration provided here
  • AddHttpMessageHandler wires up our custom DelegatingHandler to instances of this named HttpClient

Making requests

Now when you inject NetsuiteService elsewhere, you can use it to make requests to NetSuite just like this:

1
2
3
4
5
6
7
var parameters = new Dictionary<string, string>
{
{ "method", "getContact" },
{ "contactId", "5" }
};

var myContact = await _netsuiteService.GetAsync<ContactDto>(parameters);

And all of the OAuth is done quietly for you. Nice!

Please leave a comment if you found this helpful or have any questions.

The full source code of this example is available on GitHub.