Open Source API Gateway

500 Internal Server Error: NTLM Authentication Failed

Membrane API Gateway returns 500 Internal Server Error with the detail Could not authenticate! when the ntlm plugin cannot complete the NTLM handshake with a backend that asked for NTLM authentication. The backend's handshake, the connection to it or the flow configuration is to blame. Wrong credentials do not end up here: they produce a 401 from the backend.

The Error Response

The response contains the status 500 Internal Server Error, the title Internal server error., the detail Could not authenticate!, the problem type https://membrane-api.io/problems/internal and a link to this page. In development mode, it also contains the fields domain, workstation and originalRequestUrl and the exception message. The type internal means that the gateway could not complete the processing of the exchange. In production mode, the title is replaced by Internal Server Error and the details are left out.

How the NTLM Plugin Works

The ntlm plugin works in the response flow. When the backend answers with WWW-Authenticate: NTLM, the plugin takes the user name and password from the request headers configured with user and pass and runs the NTLM handshake with the backend over the same connection. The error occurs when this handshake fails. Because it happens in the response flow, the client always receives 500.

Common Causes

The Backend Sends No Valid NTLM Challenge

The backend's answer to the first handshake message has no WWW-Authenticate header, or one without an NTLM challenge, for example just NTLM or Negotiate, or the challenge is not a valid NTLM message.

The Connection Breaks During the Handshake

NTLM authenticates one connection, which has to stay open between the handshake steps. If the backend or a device in between closes or re-balances the connection, the handshake fails.

The Request URL Cannot Be Built

The URL used for the handshake is built from the request's Host header and path. An invalid Host header makes this fail.

The Response Does Not Come from a Backend

If the response was produced by a plugin such as return instead of a backend, there is no backend connection for the handshake. The plugin then fails before it looks at the response, and the generic error handler reports it under this URL.

An Unexpected Exception in the Plugin

Any other exception that the ntlm plugin does not turn into an error response itself is reported under the same URL by Membrane's generic error handler. The detail then reads Aborting! Exception caused in … flow. and names the plugin, the request URI and the flow. Unless the exception points to your configuration or to the message, this usually indicates a bug in Membrane.

How to Fix It

  1. Gateway operator: check that the backend offers plain NTLM, not only Negotiate or Kerberos, and keeps the connection open between the handshake steps.
  2. Gateway operator: check network devices between gateway and backend that close or re-balance connections.
  3. Gateway operator: use ntlm only in APIs whose response comes from a backend.
  4. Client side: send a valid Host header.
  5. Use domain, workstation and originalRequestUrl from the response (development mode), or the log, to see the handshake parameters and the exception.

A minimal configuration that reads the credentials from two request headers:

api:
port: 2000
flow:
- ntlm:
user: X-Username
pass: X-Password
target:
host: localhost
port: 8111
api:
  port: 2000
  flow:
    - ntlm:
        user: X-Username
        pass: X-Password
  target:
    host: localhost
    port: 8111

Hide Details in Production

By default, Membrane runs in development mode and adds details to error responses that help you find the problem. These details may reveal information about your setup. In production, turn them off:

configuration:
production: true
configuration:
  production: true

Add the configuration document to your apis.yaml and separate it from the APIs with ---. The title then reads Internal Server Error, and instead of the details the response only contains a log key. Search the gateway log for this key to find the exception.

If the plugin is nested inside a request, response or abort element and throws an unexpected exception, the exception message, the stack trace and the link to this page are currently sent even in production mode. Fix the failing plugin rather than relying on production mode to hide them.

Related Documentation

Configuration reference:

← All Membrane error messages

Still stuck? See the troubleshooting guide or ask on GitHub Issues. For commercial support, contact info@predic8.de.