The NATS authentication callout mechanism lets you verify credentials dynamically by invoking an external service, making it a powerful tool for sophisticated authentication flows.

Let’s see this in action. Imagine a NATS service that needs to grant access based on a JWT issued by an external identity provider. Instead of embedding all the logic within NATS itself, we can delegate this verification.

Here’s a simplified NATS server configuration snippet demonstrating the callout setup:

# nats-server.conf
auth_callout {
  url: "http://localhost:8080/verify"
  timeout: "5s"
  headers {
    "X-Auth-Service": "MyCustomAuth"
  }
}

listen: 4222

When a client attempts to connect with credentials, NATS will POST these credentials to http://localhost:8080/verify. The external service at that URL is expected to respond with a JSON payload indicating whether the credentials are valid and what permissions should be granted.

A successful verification might look like this:

Client Connection Attempt:

CONNECT {"auth": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}

NATS Server (POST to callout service):

POST /verify HTTP/1.1
Host: localhost:8080
Content-Type: application/json
X-Auth-Service: MyCustomAuth
Content-Length: 123

{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}

External Auth Service Response:

{
  "ok": true,
  "permissions": {
    "allow": ["sub.>`user.john.doe`.*"],
    "disallow": []
  }
}

NATS then uses the permissions field to authorize the client’s subsequent actions. If ok is false, the connection is rejected.

The problem this solves is the need for NATS to integrate with existing, complex authentication and authorization systems without NATS itself needing to understand the intricacies of those systems. This could involve anything from OAuth2 token validation to complex role-based access control lookups against a corporate directory.

Internally, when auth_callout is configured, NATS intercepts the initial CONNECT frame. It extracts the auth field (or other fields if configured via auth_callout.data_field) and sends it as a JSON payload to the specified url. The timeout parameter is crucial for preventing connections from hanging indefinitely if the external service is slow or unresponsive. The headers field allows you to pass additional context or API keys to your authentication service.

The permissions object in the response is where the real power lies for authorization. allow and disallow fields accept NATS permission strings. For instance, sub.> allows subscriptions to any subject prefixed with sub., and > allows any subject. You can specify fine-grained control, like user.john.doe.* to permit access only to subjects starting with user.john.doe..

A common pitfall is expecting the callout service to handle the entire connection handshake. The callout is only for authentication and authorization. NATS still manages the actual connection lifecycle, heartbeats, and protocol framing. Another detail is that the callout is performed before the INFO message is sent back to the client. If the callout fails, the client receives an error and the connection is immediately terminated.

The auth_callout.data_field setting allows you to specify which field in the initial CONNECT payload NATS should extract and send to the callout service, defaulting to auth. You can also use auth_callout.data_field multiple times to send multiple fields.

The next hurdle you’ll likely encounter is handling multiple authentication methods or tiered access, which might require more advanced callout configurations or a more sophisticated external authentication service.

Want structured learning?

Take the full Nats course →