Skip to Content
  1. Home
  2. /
  3. Blog
  4. /
  5. Customizing OAuth2 Parameters in ServiceNow for NetSuite Integration
Wednesday, July 16, 2025

Customizing OAuth2 Parameters in ServiceNow for NetSuite Integration

When integrating ServiceNow with external systems using OAuth2, you might encounter scenarios where the default OAuth2 implementation doesn't quite meet the requirements of your authorization server. I recently faced this challenge while integrating ServiceNow with NetSuite, where the standard OAuth2 flow needed parameter modifications to work correctly.

In this post, I'll show you how to extend ServiceNow's OAuthUtil script include and customize the interceptRequestParameters function to modify OAuth2 token request parameters. This technique is particularly useful for NetSuite integrations but applies to any OAuth2 integration requiring custom parameter handling.

Understanding the Challenge

ServiceNow's OAuth2 implementation works well for most standard OAuth2 flows. However, some systems like NetSuite have specific requirements that don't align perfectly with ServiceNow's defaults. According to Oracle's documentation, NetSuite's OAuth2 implementation requires:

  • code_verifier with SHA256 hashing: NetSuite requires the code_verifier parameter to be hashed using SHA256
  • code_verifier length validation: The code_verifier must be between 43 and 128 characters
  • code_challenge_method must be SHA256: When using code_challenge, the code_challenge_method parameter must be set to 'S256'
  • PKCE parameter relationship: If code_challenge is configured, code_challenge_method is mandatory
  • Specific parameter formats for grant types
  • Custom scope definitions
  • Additional custom parameters for token exchange

The default ServiceNow OAuth2 flow doesn't provide a straightforward way to modify these parameters through configuration alone. This is where extending the OAuthUtil script include becomes essential.

The Solution: Extending OAuthUtil

ServiceNow provides the OAuthUtil script include as part of its OAuth2 implementation. By creating a custom script include that extends OAuthUtil, we can override the interceptRequestParameters function to modify request parameters before they're sent to the authorization server.

Here's how to implement this solution:

Step 1: Create a Custom Script Include

First, create a new Script Include that extends OAuthUtil:

JavaScript
var CustomOAuthUtil = Class.create();
CustomOAuthUtil.prototype = Object.extendsObject(OAuthUtil, {
    initialize: function() {
        OAuthUtil.prototype.initialize.call(this);
    },

    interceptRequestParameters: function(requestParameterMap, flow, providerName) {
        // Log the incoming parameters for debugging
        gs.info('CustomOAuthUtil: Intercepting parameters for provider: ' + providerName);
        gs.info('CustomOAuthUtil: Flow type: ' + flow);
        gs.info('CustomOAuthUtil: Original parameters: ' + JSON.stringify(requestParameterMap));

        // Call the parent method first
        OAuthUtil.prototype.interceptRequestParameters.call(this, requestParameterMap, flow, providerName);

        // Customize parameters based on provider name
        if (providerName == 'NetSuite OAuth Provider') {
            this._customizeNetSuiteParameters(requestParameterMap, flow);
        }

        return requestParameterMap;
    },

    _customizeNetSuiteParameters: function(requestParameterMap, flow) {
        // Handle different OAuth flows
        if (flow == 'authorization_code') {
            // NetSuite specific modifications for authorization code flow
            
            // Modify grant_type format if needed
            if (requestParameterMap.containsKey('grant_type')) {
                var grantType = requestParameterMap.get('grant_type');
                gs.info('CustomOAuthUtil: Original grant_type: ' + grantType);
                requestParameterMap.put('grant_type', 'authorization_code');
            }

            // NetSuite requires code_verifier to be SHA256 hashed with specific length
            if (requestParameterMap.containsKey('code_verifier')) {
                var codeVerifier = requestParameterMap.get('code_verifier');
                gs.info('CustomOAuthUtil: Processing code_verifier for NetSuite');
                
                // Validate code_verifier length (43-128 characters)
                if (codeVerifier.length < 43 || codeVerifier.length > 128) {
                    gs.error('CustomOAuthUtil: code_verifier length must be between 43 and 128 characters. Current length: ' + codeVerifier.length);
                    // Generate a new compliant code_verifier if needed
                    codeVerifier = this._generateCompliantCodeVerifier();
                }
                
                // Apply SHA256 hash to code_verifier
                var hashedVerifier = this._hashCodeVerifier(codeVerifier);
                requestParameterMap.put('code_verifier', hashedVerifier);
                gs.info('CustomOAuthUtil: Applied SHA256 hash to code_verifier');
            }

            // NetSuite requires code_challenge_method to be SHA256 when code_challenge is present
            if (requestParameterMap.containsKey('code_challenge')) {
                // Ensure code_challenge_method is set to S256
                requestParameterMap.put('code_challenge_method', 'S256');
                gs.info('CustomOAuthUtil: Set code_challenge_method to S256 for NetSuite');
                
                // If code_challenge exists but code_challenge_method doesn't, add it
                if (!requestParameterMap.containsKey('code_challenge_method')) {
                    gs.warn('CustomOAuthUtil: code_challenge present without code_challenge_method - adding S256');
                    requestParameterMap.put('code_challenge_method', 'S256');
                }
            }

            // Modify scope format
            if (requestParameterMap.containsKey('scope')) {
                var scope = requestParameterMap.get('scope');
                // NetSuite expects space-separated scopes
                scope = scope.replace(/,/g, ' ');
                requestParameterMap.put('scope', scope);
                gs.info('CustomOAuthUtil: Modified scope format for NetSuite');
            }
        } else if (flow == 'refresh_token') {
            // Handle refresh token flow modifications
            gs.info('CustomOAuthUtil: Processing refresh token flow');
            
            // Add any refresh-specific parameter modifications here
        }

        // Log final parameters for debugging
        gs.info('CustomOAuthUtil: Modified parameters: ' + JSON.stringify(requestParameterMap));
    },

    _hashCodeVerifier: function(codeVerifier) {
        // Apply SHA256 hash to the code_verifier
        var crypto = new GlideCertificateEncryption();
        var hashedBytes = crypto.generateMac(codeVerifier, 'SHA-256', '');
        
        // Convert to base64url encoding (URL-safe base64)
        var base64 = GlideStringUtil.base64Encode(hashedBytes);
        // Make it URL-safe by replacing characters
        var base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
        
        return base64url;
    },

    _generateCompliantCodeVerifier: function() {
        // Generate a code_verifier that meets NetSuite's requirements (43-128 chars)
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
        var length = 64; // A safe middle ground
        var verifier = '';
        
        for (var i = 0; i < length; i++) {
            verifier += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        
        gs.info('CustomOAuthUtil: Generated compliant code_verifier with length: ' + verifier.length);
        return verifier;
    },

    type: 'CustomOAuthUtil'
});

Step 2: Configure Your OAuth Provider

After creating the custom script include, you need to configure your OAuth provider to use it:

  1. Navigate to System OAuth > Application Registry
  2. Open your NetSuite OAuth provider record
  3. In the OAuth Entity Profile or OAuth Entity Scope, update the Script Include field to reference your custom script: CustomOAuthUtil

Step 3: Advanced Parameter Modifications

For more complex scenarios, you can implement sophisticated parameter handling:

JavaScript
_customizeNetSuiteParameters: function(requestParameterMap, flow) {
    // Dynamic parameter modification based on instance settings
    var netsuiteConfig = this._getNetSuiteConfiguration();
    
    if (flow == 'authorization_code') {
        // Handle dynamic client assertions if needed
        if (netsuiteConfig.requiresClientAssertion) {
            var assertion = this._generateClientAssertion(netsuiteConfig);
            requestParameterMap.put('client_assertion', assertion);
            requestParameterMap.put('client_assertion_type', 
                'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
            
            // Remove client_secret if using assertion
            if (requestParameterMap.containsKey('client_secret')) {
                requestParameterMap.remove('client_secret');
            }
        }
        
        // Handle custom redirect URI encoding
        if (requestParameterMap.containsKey('redirect_uri')) {
            var redirectUri = requestParameterMap.get('redirect_uri');
            // NetSuite might require specific encoding
            var encodedUri = this._customEncodeUri(redirectUri);
            requestParameterMap.put('redirect_uri', encodedUri);
        }
        
        // Add NetSuite-specific headers as parameters if needed
        if (netsuiteConfig.customHeaders) {
            for (var header in netsuiteConfig.customHeaders) {
                requestParameterMap.put(header, netsuiteConfig.customHeaders[header]);
            }
        }
    }
},

_getNetSuiteConfiguration: function() {
    // Retrieve configuration from system properties or custom table
    return {
        requiresClientAssertion: gs.getProperty('netsuite.oauth.use_assertion', 'false') == 'true',
        customHeaders: {
            'X-NetSuite-Application-Id': gs.getProperty('netsuite.application.id', '')
        }
    };
},

_generateClientAssertion: function(config) {
    // Implement JWT generation for client assertion
    // This is a simplified example - implement proper JWT signing
    var header = {
        "alg": "RS256",
        "typ": "JWT"
    };
    
    var claims = {
        "iss": gs.getProperty('oauth.client.id'),
        "sub": gs.getProperty('oauth.client.id'),
        "aud": gs.getProperty('netsuite.token.endpoint'),
        "exp": Math.floor(Date.now() / 1000) + 300,
        "iat": Math.floor(Date.now() / 1000)
    };
    
    // In production, implement proper JWT signing
    return "generated.jwt.token";
},

_customEncodeUri: function(uri) {
    // Implement custom URI encoding if NetSuite requires it
    // Some systems have specific encoding requirements
    return encodeURI(uri).replace(/%20/g, '+');
}

Step 4: Debugging and Troubleshooting

To effectively debug OAuth2 parameter issues:

  1. Enable OAuth Debug Logging:
JavaScript
// Add comprehensive logging in your custom script
interceptRequestParameters: function(requestParameterMap, flow, providerName) {
    var debugEnabled = gs.getProperty('oauth.debug.enabled', 'false') == 'true';
    
    if (debugEnabled) {
        gs.info('=== OAuth Parameter Modification Debug ===');
        gs.info('Provider: ' + providerName);
        gs.info('Flow: ' + flow);
        gs.info('Parameters Before: ' + this._mapToString(requestParameterMap));
    }
    
    // Perform modifications
    this._customizeNetSuiteParameters(requestParameterMap, flow);
    
    if (debugEnabled) {
        gs.info('Parameters After: ' + this._mapToString(requestParameterMap));
        gs.info('=== End OAuth Debug ===');
    }
    
    return requestParameterMap;
},

_mapToString: function(map) {
    var result = {};
    var keys = map.keySet().toArray();
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        var value = map.get(key);
        // Mask sensitive values
        if (key.toLowerCase().indexOf('secret') > -1 || 
            key.toLowerCase().indexOf('password') > -1) {
            result[key] = '***MASKED***';
        } else {
            result[key] = value;
        }
    }
    return JSON.stringify(result);
}
  1. Test Different Scenarios:
JavaScript
// Create a test script include to validate parameter modifications
var OAuthParameterTest = Class.create();
OAuthParameterTest.prototype = {
    testNetSuiteParameters: function() {
        var customOAuth = new CustomOAuthUtil();
        var testMap = new Packages.java.util.HashMap();
        
        // Simulate authorization code flow
        testMap.put('grant_type', 'authorization_code');
        testMap.put('code', 'test_auth_code');
        testMap.put('redirect_uri', 'https://instance.service-now.com/oauth_redirect.do');
        testMap.put('code_verifier', 'test_verifier');
        
        customOAuth.interceptRequestParameters(testMap, 'authorization_code', 'NetSuite OAuth Provider');
        
        // Verify modifications
        gs.info('Test Results:');
        gs.info('code_verifier removed: ' + !testMap.containsKey('code_verifier'));
        gs.info('grant_type value: ' + testMap.get('grant_type'));
    },
    
    type: 'OAuthParameterTest'
};

Best Practices and Considerations

When implementing OAuth2 parameter customization:

  1. Security First: Never log sensitive information like client secrets or tokens
  2. Provider-Specific Logic: Use provider names to ensure modifications only apply to specific integrations
  3. Maintain Compatibility: Always call the parent method to preserve default ServiceNow functionality
  4. Error Handling: Implement try-catch blocks to prevent integration failures
  5. Documentation: Document all parameter modifications for future maintenance

NetSuite-Specific PKCE Requirements

NetSuite has unique requirements for PKCE (Proof Key for Code Exchange) parameters that differ from standard OAuth2 implementations:

code_verifier Requirements

  1. SHA256 Hashing: The code_verifier must be hashed using SHA256
  2. Length Validation: The code_verifier must be between 43 and 128 characters
  3. Base64URL Encoding: The hashed value must use URL-safe base64 encoding

code_challenge and code_challenge_method Relationship

According to the PKCE specification (RFC 7636), NetSuite enforces these rules:

  1. code_challenge_method is mandatory: If code_challenge is present, code_challenge_method MUST also be included
  2. SHA256 only: The code_challenge_method parameter must be set to 'S256' (SHA256)
  3. Parameter association: Both parameters are associated with the authorization code for verification

Here's a complete implementation that handles these requirements:

JavaScript
_customizeNetSuiteParameters: function(requestParameterMap, flow) {
    if (flow == 'authorization_code') {
        // 1. Handle code_verifier with SHA256 hashing
        if (requestParameterMap.containsKey('code_verifier')) {
            var codeVerifier = requestParameterMap.get('code_verifier');
            
            // Validate length
            if (codeVerifier.length < 43 || codeVerifier.length > 128) {
                gs.warn('Code verifier length (' + codeVerifier.length + ') outside required range');
                // You may want to throw an error or regenerate
            }
            
            // Apply SHA256 hash
            var hashedVerifier = this._hashCodeVerifier(codeVerifier);
            requestParameterMap.put('code_verifier', hashedVerifier);
        }
        
        // 2. Handle code_challenge and code_challenge_method relationship
        if (requestParameterMap.containsKey('code_challenge')) {
            // NetSuite requires code_challenge_method when code_challenge is present
            if (!requestParameterMap.containsKey('code_challenge_method')) {
                gs.warn('code_challenge present without code_challenge_method - adding S256');
            }
            // Always set to S256 for NetSuite
            requestParameterMap.put('code_challenge_method', 'S256');
        }
        
        // 3. Ensure correct grant_type format
        requestParameterMap.put('grant_type', 'authorization_code');
        
        // 4. Add NetSuite-specific parameters
        if (!requestParameterMap.containsKey('access_type')) {
            requestParameterMap.put('access_type', 'offline');
        }
        
        // 5. Format scope correctly for NetSuite
        if (requestParameterMap.containsKey('scope')) {
            var scope = requestParameterMap.get('scope');
            // NetSuite expects space-separated scopes
            scope = scope.replace(/,/g, ' ');
            requestParameterMap.put('scope', scope);
        }
    }
},

_hashCodeVerifier: function(codeVerifier) {
    // Use ServiceNow's crypto utilities for SHA256
    var crypto = new GlideCertificateEncryption();
    var hashedBytes = crypto.generateMac(codeVerifier, 'SHA-256', '');
    
    // Convert to base64url (URL-safe base64)
    var base64 = GlideStringUtil.base64Encode(hashedBytes);
    return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

Conclusion

Customizing OAuth2 parameters in ServiceNow by extending the OAuthUtil script include provides the flexibility needed to integrate with systems like NetSuite that have specific authentication requirements. This approach maintains the integrity of ServiceNow's OAuth2 framework while allowing for the customizations necessary for successful integration.

Remember to thoroughly test your modifications in a development environment and monitor the OAuth2 flow closely when first implementing these changes in production. The ability to intercept and modify request parameters gives you complete control over the OAuth2 token exchange process, ensuring successful integration with even the most demanding external systems.