# Help with NTLM Authentication and POST commands in 8.3

**URL:** <https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465>\
**Category:** Ignition\
**Tags:** ignition83, scripting\
**Created:** [November 26, 2025, 8:49pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465 "2025-11-26T20:49:20Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Lynn\_Martineau](https://avatars.discourse-cdn.com/v4/letter/l/f1d935/32.png) [@Lynn\_Martineau](https://forum.inductiveautomation.com/u/Lynn_Martineau)\
**Post date:** [November 26, 2025, 8:49pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/1 "2025-11-26T20:49:20Z")

</div>

I need to make API calls to a system that will only allow authentication via NTLM in Ignition 8.3.  
After being authenticated, I need to POST to two APIs as an authenticated user.  
I’m writing a Project Library script that will be called via a Gateway Tag Change event.  
The tag change will have access to parameter values that need to be sent in the POSTs.

I haven’t figured out how to set the headers or the body/params yet.

Here’s what I have so far:

* * *

```python
""" 
This will call three APIs to step through the process of printing a Material Tag.
The material must have been previously accepted and must not be expired.
    1. login so the server can trust Search and Print requests
    2. query to get acceptance details
    3. update the JSON record with process info from the PLC and Ignition
    4. send the updated JSON to create quantity details and print a tag
    Note: Password changes are maintained in KeePass2 shared file
"""

# import libraries recommended by Ryan McLaughlin  

from org.apache.http.auth import AuthScope
from org.apache.http.auth import NTCredentials
from org.apache.http.client.methods import HttpGet
from org.apache.http.client.methods import HttpPost # added because POST is required
from org.apache.http.impl.client import DefaultHttpClient
from org.apache.http.impl.client import BasicResponseHandler
from org.apache.http.entity import ContentType
from java.io import ByteArrayOutputStream

# initialize variables from tag values
tagPaths = [
    '[NG]Costpoint/LoginUser',
    '[NG]Costpoint/Search',
    '[NG]Costpoint/Print',
    '[NG]Costpoint/Database',
    '[NG]Costpoint/Domain',
    '[NG]Costpoint/Username',
    '[NG]Costpoint/Password'
]
tagValues = system.tag.readBlocking(tagPaths)

loginUrl = tagValues[0].value
searchUrl = tagValues[1].value
printUrl = tagValues[2].value
database = tagValues[3].value
domain = tagValues[4].value
username = tagValues[5].value
password = tagValues[6].value

def loginUser(moNumber, quantity, printer, labelCount):
    # Setup the client with NTLM Auth
    httpclient = DefaultHttpClient()
    
    # Define the credentials to use
    creds = NTCredentials(username, password, system.net.getHostName(), domain)
    print('creds={}'.format(creds))
    
    httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, creds)
    print('httpclient={}'.format(httpclient))
    
    # Define the host and URL to call
    httppost = HttpPost(loginUrl)
    print('httppost={}'.format(httppost))
    
    # Add headers
    header = {'Accept':'application/json'}
    try:
        httppost.addHeader(header)
    except:
        print('error trying to addHeader')
    
    # Execute the request
    response = httpclient.execute(httppost)
    print('response={}'.format(response))
    
    # Get response entity and MIME type
    entity = response.getEntity()
    print('entity={}'.format(entity))
    
    # Process the content
    contentType = ContentType.getOrDefault(response.getEntity()).getMimeType().lower()
    print('contentType={}'.format(contentType))
    
# if 'json' in contentType:
# return system.util.jsonDecode(BasicResponseHandler().handleResponse(response))
# elif contentType in ["image/png", "image/jpeg"]:
# baos = ByteArrayOutputStream()
# entity.writeTo(baos)
# return baos.toByteArray(), contentType
# else:
# return BasicResponseHandler().handleResponse(response), contentType
    
    httppost.releaseConnection()
    
    # step 2 search for the material tag
    httppost = HttpPost(searchUrl)
        
    searchParams = {'findBy':'MO','findText':moNumber,'findSerLot':'','database':database}
    try:
        httppost.setParams(searchParams)
    except:
        print('error trying to setParams')
    
    jsonResponse = httpclient.execute(httppost)
    print('jsonResponse={}'.format(jsonResponse))
    # step 3 update elements of the search results and print a material tag
    #jsonResponse['searchResults'][0]['quantity'] = quantity
    #jsonResponse['searchResults'][0]['labelCount'] = labelCount
    
    ###
    ###
    
    printPayload={'printer':printer,'userId':username,'userDisplayName':'Ignition','database':database,'labelRecords':jsonResponse}

```

* * *

This is on a parent project just printing via this script in the script console

```python
moNumber = '33PM097734'
quantity = 19.4
printer = 'PRN060050'
labelCount = 1

AcceptTag.tagTest.loginUser(moNumber, quantity, printer, labelCount)

```

* * *

Printed output is:

```python
creds=[principal: myDomain\myUsername][workstation: myWorkstation]
httpclient=org.apache.http.impl.client.DefaultHttpClient@4b841149
httppost=POST http://myURL/LoginUser HTTP/1.1
error trying to addHeader
response=HTTP/1.1 415 Unsupported Media Type [Transfer-Encoding: chunked, Server: Microsoft-IIS/10.0, Persistent-Auth: true, X-Powered-By: ASP.NET, Date: Wed, 26 Nov 2025 20:31:34 GMT] org.apache.http.conn.BasicManagedEntity@70e60d8c
entity=org.apache.http.conn.BasicManagedEntity@70e60d8c
contentType=text/plain
error trying to setParams
jsonResponse=HTTP/1.1 415 Unsupported Media Type [Transfer-Encoding: chunked, Server: Microsoft-IIS/10.0, X-Powered-By: ASP.NET, Date: Wed, 26 Nov 2025 20:31:34 GMT] org.apache.http.conn.BasicManagedEntity@59e15099

```

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [November 26, 2025, 9:04pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/2 "2025-11-26T21:04:57Z")

</div>

> [@Lynn\_Martineau](#):
>
> I haven’t figured out how to set the headers or the body/params yet.

> [@Lynn\_Martineau](#):
>
> `error trying to addHeader`

See [AbstractHttpMessage (Apache HttpCore 4.4.13 API)](https://javadoc.io/static/org.apache.httpcomponents/httpcore/4.4.13/org/apache/http/message/AbstractHttpMessage.html#addHeader(org.apache.http.Header))

It doesn't accept a dictionary, it accepts either 2 string parameters or a Header object.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [November 26, 2025, 9:20pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/3 "2025-11-26T21:20:15Z")

</div>

Be aware that gateway events will use the account that is running the service--you may need to change the service to run under a user with appropriate privileges.

---

<div class="post-metadata">

**Author:** ![Lynn\_Martineau](https://avatars.discourse-cdn.com/v4/letter/l/f1d935/32.png) [@Lynn\_Martineau](https://forum.inductiveautomation.com/u/Lynn_Martineau)\
**Post date:** [November 26, 2025, 10:13pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/4 "2025-11-26T22:13:24Z")

</div>

The service account running the gateway is registered with the API service.  
I have a working Python version that runs start-to-finish written in Visual Studio Code so I know the credentials are good.

The VSC version uses the `from requests_ntlm import HttpNtlmAuth` library.

Thanks @Kevin.Herron, the header problem is fixed.  
Still get `HTTP/1.1 415 Unsupported Media Type`

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [November 26, 2025, 10:49pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/5 "2025-11-26T22:49:10Z")

</div>

Consider running wireshark while testing with the gateway and then testing with CPython. Compare.

---

<div class="post-metadata">

**Author:** ![Lynn\_Martineau](https://avatars.discourse-cdn.com/v4/letter/l/f1d935/32.png) [@Lynn\_Martineau](https://forum.inductiveautomation.com/u/Lynn_Martineau)\
**Post date:** [December 4, 2025, 2:23pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/6 "2025-12-04T14:23:32Z")

</div>

Our IT department said wireshark is not justified for this issue.

The DEV team that wrote the APIs I’m trying to call said that NTLM is the only authentication method they will support and,

> [@Calling Web API using windows(NTLM) authentication](https://forum.inductiveautomation.com/t/calling-web-api-using-windows-ntlm-authentication/90365/2):
>
> There's no support for NTLM authentication in either the old `system.net` functions or the newer `system.net.httpClient` functions. You won't be able to call this API if that's the only authentication mechanism supported.

I think I’ll have to ask the customer (internal) to drop this requirement 😩

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 4, 2025, 2:53pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/7 "2025-12-04T14:53:40Z")

</div>

As noted in other topics, this is supported in the Apache HTTP client jars that come with Ignition. Like this one:

> [@Download document from SharePoint (NTLM Authentication)](https://forum.inductiveautomation.com/t/download-document-from-sharepoint-ntlm-authentication/69474/8):
>
> Thanks @Matthew.gaitan that was the last part I needed My final code: # Perform a basic HTTP Get to the provided URL using the NTLM auth in the function def DownloadFile(remoteFile, localFile): from org.apache.http.auth import AuthScope from org.apache.http.auth import NTCredentials from org.apache.http.client.methods import HttpGet from org.apache.http.impl.client import DefaultHttpClient from org.apache.http.impl.client import BasicResponseHandler from org.apache.http.entity import Co…

Your code is missing something or doing something wrong and it isn't clear based on what you've shared.

Your IT department is simply wrong. Use a packet capture to compare to working requests so you can see the differences. I would suspect some aspect of how your gateway service account is set up, but the possibilities are endless. (Are you running your Python3 test code in a service with the same account as Ignition? Desktop and service environments are different.)

---

<div class="post-metadata">

**Author:** ![Lynn\_Martineau](https://avatars.discourse-cdn.com/v4/letter/l/f1d935/32.png) [@Lynn\_Martineau](https://forum.inductiveautomation.com/u/Lynn_Martineau)\
**Post date:** [December 4, 2025, 7:33pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/8 "2025-12-04T19:33:27Z")

</div>

I have _not_ tried running native Python on the server, only from my workstation so that is probably why VSCode Python succeeds vs. the service account that is running Ignition.

I agree; wireshark would be helpful and the IT dept. knows it would help but the project is too low of a priority for them / not worth their time so they won’t allow installing wireshark on the server.

I have another version of code based on what I found here:  
[Webservices NTLM Auth - General Discussion - Inductive Automation Forum](https://forum.inductiveautomation.com/t/webservices-ntlm-auth/12656)

This version returns a PKIX certificate error.  
The needed certificates are in the …\Certificates\Supplemental folder, downloaded from the web site hosting the API and confirmed by their developers.

```python
def useApacheLibs(username, password):
    from org.apache.http.auth import AuthScope
    from org.apache.http.auth import NTCredentials
    from org.apache.http.client.methods import HttpGet
    from org.apache.http.client.methods import HttpPost
    from org.apache.http.impl.client import DefaultHttpClient
    from org.apache.http.impl.client import BasicResponseHandler
    from org.apache.http.entity import ContentType
    from java.io import ByteArrayOutputStream
    
    # Setup the client with NTLM Auth
    httpclient = DefaultHttpClient()
    print 'httpclient={}'.format(httpclient)
    # Define the credentials to use
    creds = NTCredentials(username, password, system.net.getHostName(), "myDomain")
    httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, creds)
    print 'creds={}'.format(creds)
    print 'httpclient={}'.format(httpclient)
    # Define the host and URL to call
    getAppInfoUrl = '{}/GetAppInfo'.format(baseUrl)
    print 'getAppInfoUrl={}'.format(getAppInfoUrl)
    
    httpget = HttpGet(getAppInfoUrl)
    print 'httpget={}'.format(httpget)
    
    # Execute the request
    response = httpclient.execute(httpget)
    print 'response={}'.format(response)
    return response

```

Printed info looks good up to the `execute` statement where it blows up with PKIX error.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [December 4, 2025, 8:02pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/9 "2025-12-04T20:02:49Z")

</div>

Apache's HTTP client is not aware of our `supplemental` certificates pattern and so is building a chain of trust that doesn't include your added certs.

You'll have to figure out how to inject the certs from the stored file location into the Apache HTTP client instance.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [December 4, 2025, 8:04pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/10 "2025-12-04T20:04:53Z")

</div>

Huh, that's a little surprising, because our supplement cert pattern is nothing special - we just import those certs into the JDK's root keystore.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [December 4, 2025, 8:10pm UTC](https://forum.inductiveautomation.com/t/help-with-ntlm-authentication-and-post-commands-in-8-3/112465/11 "2025-12-04T20:10:31Z")

</div>

Maybe if you use `org.apache.http.impl.client.HttpClientBuilder` and the `setSSLContext` method to recycle the default from the system (`javax.net.ssl.SSLContext#getDefault`) it'll "just work"?
