NetSuite Integration: Mastering OAuth 2.0 Client Credentials Flow in .NET Framework (Step-by-Step Guide with Source Code)

Introduction :

Hey there! Let me tell you about this project I tackled recently.

I was given the assignment to integrate NetSuite, and the task was to implement the OAuth 2.0 Client Credentials Flow, also known as M2M (Machine to Machine).

Now, there’s a handy SDK for this in .NET Core, but guess what? There wasn’t one for the old .NET Framework.

So, I had to roll up my sleeves and dive deep into research mode.

I started by checking out the .NET Core library to understand how it worked. It was like being a detective, piecing together clues.

I had to recreate that code for the .NET Framework. Let me tell you, it wasn’t easy.

There were a lot of trial and error moments, testing different .NET libraries to get the authentication just right.

But after much persistence and some coffee-fueled late nights, I finally cracked it!

Now, I know others are struggling with the same issue. I wanted to share my journey and the solution I came up with.

Here’s the entire process of implementing the OAuth 2.0 Client Credentials Flow with a source code example.

I hope this helps save you some time and headaches!

Note: If you use .NET Core, you can use this [GitHub project] (https://github.com/ericpopivker/entech-blog-netsuite-oauth-m2m-demo) to implement OAuth 2.0 Client Credentials Flow. You can skip reading this article because an SDK is already available for .NET Core projects. Please use it.

Flowchart showcasing the OAuth 2.0 authentication process with Microsoft .NET, NetSuite, user agents, clients, authentication server, and resource server.

Before We Dive Into Understanding Bouncy Castle and OAuth 2.0

What is Bouncy Castle? When to Use It?

Bouncy Castle is an open-source library that provides cryptographic APIs for JAVA and C# .NET. It supports a wide range of cryptographic algorithms and is widely used for handling encryption, decryption, key generation, and more.

When to Use Bouncy Castle:

  • When you need advanced cryptographic operations that are not natively supported in .NET.
  • For handling various encryption algorithms and formats, including those required for OAuth 2.0 authentication.
  • For managing private keys and certificate handling.

Diagram of software architecture layers for cryptography, featuring TLS, PGP, Java JCA Provider, Bouncy Castle API, and NetSuite OAuth 2.0 among others.
Image source: Bouncy Castle

What is OAuth 2.0 and How Does OAuth 2.0 Work?

OAuth is an open standard for access delegation, commonly used for token-based authentication and authorization. OAuth 1.0 and OAuth 2.0 are two versions of this protocol.

OAuth 2.0, which stands for “Open Authorization“, OAuth 2.0 is a set of protocols that enables developers to outsource user authentication and authorization to someone else easily. While the specifications do not expressly address authentication, in reality, it is a critical component of OAuth, therefore we will go over it in detail (since that is how we roll).

OAuth 2.0 enables consented access and limits the actions that the client app can conduct on the user’s resources without ever sharing the user’s credentials.

How Does OAuth 2.0 Work?

None of the specifications explain how OAuth is implemented into apps.
Whoops! But as a developer, that is what you are concerned about. They also do not address the many workflows or processes that use OAuth. They leave practically everything to the implementer (the person who creates the OAuth Server) and integrator.

Instead of simply rewording the information in the specifications (again), let us develop a vocabulary for real-world OAuth integrations and implementations. We’ll name these OAuth modes.
There are now 8 OAuth modes that are generally used. The real-world OAuth modes are:

  1. Local login and signup.
  2. Third-party authentication and registration (federated identity)
  3. First-party login and registration (reversed federated identity).
  4. Enterprise login and registration (a federated identity with a twist)
  5. Third-party service authorization
  6. First-party service authorization
  7. Machine-to-machine authentication and authorization
  8. Device login and registration.

I’ve added a notation to a handful of the items above to indicate which are federated identity workflows.
Flowchart illustrating the NetSuite OAuth 2.0 process: Resource Owner, Client, Authorization Server, and Resource Server involved in steps to access a resource using credentials and access tokens.

Why OAuth 2.0 is Required in This Scenario?

  • Ease of Implementation: OAuth 2.0 is easier to implement compared to OAuth 1.0, reducing the complexity of integration.
  • Security: OAuth 2.0 provides enhanced security features, making it suitable for sensitive data transactions like those with NetSuite.
  • Flexibility: The client credentials grant type in OAuth 2.0 is ideal for server-to-server communication where user interaction is not required.

Now, Let’s Setting Up OAuth 2.0 Client Credentials in .NET Framework for NetSuite

Prerequisites:
First, ensure you have installed the [BouncyCastle.Cryptography library](https://www.nuget.org/packages/BouncyCastle.Cryptography/2.3.0) from NuGet for .NET OAuth 2.0 authentication:

Install-Package BouncyCastle.Cryptography

Handling Private Keys with BouncyCastle

When dealing with private keys, they must be formatted correctly. If Bouncy Castle encounters any parsing issues, the Org.BouncyCastle.Pkcs.Pkcs8Generator class can be utilized to handle PKCS#8 formatted keys effectively.

Setting Up Variables and Classes:

Here are the class definitions required for handling NetSuite tokens and setting up the integration:


public class NetSuiteToken
{
	[JsonProperty("access_token")] // JSON property for access token obtained from OAuth.
	public string AccessToken { get; set; }

	[JsonProperty("expires_in")] // JSON property for token expiry time in seconds.
	public string ExpiresAfterSeconds { get; set; }
}

public sealed class NetSuiteIntegration
{
	public string AccountId { get; set; } // Unique identifier for the NetSuite account.
	public string ConsumerKey { get; set; } // Consumer key provided by NetSuite for OAuth.
	public string ClientCredentialsCertificateId { get; set; } // Certificate ID for client credentials.
	public string BaseURL { get; set; } // Base URL for NetSuite API requests.
	public string AccessToken { get; set; } // Access token for authenticated requests.
	public DateTime? TokenExpireUTC { get; set; } // Expiry time of the access token.
	public string PrivateCertificate { get; set; } // Private certificate key as a string.
}

Defining OAuth2 API Endpoints

It’s crucial to define the necessary variables for OAuth2 API endpoints clearly:


private readonly string Oauth2ApiRoot = $"https://{NetSuiteCredentials.AccountId}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1"; 
private readonly string TokenEndPointUrl = $"{Oauth2ApiRoot}/token";

Creating a JWT (JSON Web Token):

JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
To create a JWT token for authentication, follow these steps:


// Method to create a JWT token using RSA encryption and signing with a private key.
private string GetJwtToken()
{
	// Attempt to parse the private certificate into RSA key parameters.
	RsaPrivateCrtKeyParameters keyPair;
	using (var reader = new StringReader(NetSuiteCredentials.PrivateCertificate))
	{
		var pemReader = new PemReader(reader);
		keyPair = (RsaPrivateCrtKeyParameters)pemReader.ReadObject();
	}

	// Throw an exception if the private key is invalid or not in expected format.
	if (keyPair == null)
		throw new Exception("Invalid private key format.");
	// Convert the RSA key parameters for use with .NET's cryptography library.
	RSAParameters rsaParams = DotNetUtilities.ToRSAParameters(keyPair);
	RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
	provider.ImportParameters(rsaParams);

	// Create the RSA security key and set up the signing credentials using SHA-256.
	RsaSecurityKey rsaSecurityKey = new RsaSecurityKey(provider);
	var signingCreds = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256);
	signingCreds.Key.KeyId = NetSuiteCredentials.ClientCredentialsCertificateId;

	// Set the current UTC time as the issuing time of the token.
	var now = DateTime.UtcNow;
	// Create the token with issuer, audience, and scope claims.
	var tokenDescriptor = new SecurityTokenDescriptor
	{
		Issuer = NetSuiteCredentials.ConsumerKey,
		Audience = TokenEndPointUrl,
		Expires = now.AddMinutes(5),
		IssuedAt = now,
		Claims = new Dictionary<string, object> { { "scope", new[] { "rest_webservices" } } },
		SigningCredentials = signingCreds
	};

	// Generate the token using a token handler and return the serialized token.
	var tokenHandler = new JwtSecurityTokenHandler();
	var token = tokenHandler.CreateToken(tokenDescriptor);
	return tokenHandler.WriteToken(token);
}

Creating an Access Token:

With the JWT token ready, proceed to create an access token:


public async Task GetAccessToken(HttpClient httpClient)
{
	string clientAssertion = GetJwtToken();

	// Prepare the parameters for the POST request to the token endpoint.
	var requestParams = new List<KeyValuePair<string, string>>
	{
		new KeyValuePair<string, string>("grant_type", "client_credentials"),
		new KeyValuePair<string, string>("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
		new KeyValuePair<string, string>("client_assertion", clientAssertion)
	};

	// Create and send the HTTP request, then read the response.
	var httpRequest = new HttpRequestMessage(HttpMethod.Post, TokenEndPointUrl);
	httpRequest.Content = new FormUrlEncodedContent(requestParams);
	var httpResponse = await httpClient.SendAsync(httpRequest);
	var responseJson = await httpResponse.Content.ReadAsStringAsync();

	// Deserialize the JSON response into a NetSuiteToken object.
	var response = JsonConvert.DeserializeObject(responseJson);
	return response;
}

Refreshing the Access Token:

To ensure continuous access, it’s vital to refresh the token before it expires:


private async Task RefreshAccessToken()
{
	// Check if the current token is still valid before attempting a refresh.
	if (!string.IsNullOrEmpty(netSuiteCredentials.AccessToken) && DateTime.UtcNow < netSuiteCredentials.TokenExpireUTC)
		return;

	// Obtain a new access token using the authentication helper.
	AuthenticationHelper authenticationHelper = new AuthenticationHelper(netSuiteCredentials);
	var accessTokenObj = await authenticationHelper.GetAccessToken(httpClient);

	// Update stored token and expiration time if a new token was successfully obtained.
	if (!string.IsNullOrEmpty(accessTokenObj.AccessToken))
	{
		netSuiteCredentials.AccessToken = accessTokenObj.AccessToken;
		netSuiteCredentials.TokenExpireUTC = DateTime.UtcNow.AddSeconds(Convert.ToDouble(accessTokenObj.ExpiresAfterSeconds) - 300);
		netSuiteIntegrationSource.Post(netSuiteCredentials);
	}
}

Download source code

you can download the complete source code for implementing OAuth 2.0 Client Credentials Flow in .NET Framework from this GitHub repository(https://github.com/satva-git/NetSuite-OAuth-2.0-Client-Credentials-Flow-M2M-in-.NET-Framework).
In conclusion, the journey to secure NetSuite API communication via OAuth 2.0 Client Credentials Flow in a .NET Framework demands rigorous management of private keys, JWT tokens, and access tokens. By adhering to best practices in this API integration with NetSuite areas, you ensure that your application is not only secure but also robust and adaptable to meet evolving business needs.

Encountering Challenges with Your NetSuite Integration?

If you’re facing technical difficulties or seeking guidance on best Integration practices, don’t hesitate to reach out. Satva Solutions is here to assist you in navigating the complexities of NetSuite integration to ensure your project’s success

Article by

Chintan Prajapati

Chintan Prajapati, a seasoned computer engineer with over 20 years in the software industry, is the Founder and CEO of Satva Solutions. His expertise lies in Accounting & ERP Integrations, RPA, and developing technology solutions around leading ERP and accounting software, focusing on using Responsible AI and ML in fintech solutions. Chintan holds a BE in Computer Engineering and is a Microsoft Certified Professional, Microsoft Certified Technology Specialist, Certified Azure Solution Developer, Certified Intuit Developer, and Xero Developer.Throughout his career, Chintan has significantly impacted the accounting industry by consulting and delivering integrations and automation solutions that have saved thousands of man-hours. He aims to provide readers with insightful, practical advice on leveraging technology for business efficiency.Outside of his professional work, Chintan enjoys trekking and bird-watching. Guided by the philosophy, "Deliver the highest value to clients". Chintan continues to drive innovation and excellence in digital transformation strategies from his base in Ahmedabad, India.