A community member asked how to authenticate an unknown number of distributed NATS clients when each client can generate its own NKey pair and share only its public identity through a separate secure channel.
The short answer: use NATS decentralized JWT authentication for this pattern. The client can keep its private NKey local, your trusted onboarding or identity service can issue a signed user JWT containing the client public NKey and permissions, and the NATS server can validate both the trusted signer and the client’s proof that it holds the matching private key.
If you have an unknown or changing number of clients, adding every user directly to nats-server configuration does not scale well. Each add, remove, or permission change becomes a server configuration management problem.
With NATS JWT authentication, the server is configured to trust an operator rather than a static list of every end user. Accounts are signed by that operator, and users are signed by an account. A client presents its user JWT when it connects, and the server validates that the JWT chains back to the trusted operator before applying the claims in that JWT, including authorization permissions.
This pattern requires running nats-server in operator mode (decentralized JWT authentication) with a configured account resolver, rather than the static authorization block used for in-config users.
That means onboarding a new user can be handled by your own trusted service without editing server config for each new client.
A common architecture looks like this:
In simplified form:
1Client generates NKey pair2 |3 | public NKey + external identity proof4 v5Trusted onboarding service6 |7 | signed user JWT8 v9Client connects to NATS10 |11 | JWT + nonce signature12 v13NATS validates the JWT trust chain and nonce signature, then enforces permissionsThe client does not sign the JWT during every connection. The trusted issuer signs the user JWT.
When the client connects, the NATS server provides a nonce challenge. The client signs that nonce with the private NKey corresponding to the public NKey in the user JWT. The server then verifies the signature against the public key from the JWT.
This matters because it prevents the user JWT from acting as a simple bearer token. Possession of the JWT alone should not be enough; the connecting client also needs the private key that matches the public NKey in the JWT.
At connection time, the server validates two different things:
After those checks pass, the server uses the authorization claims in the JWT to determine what the client can publish, subscribe to, or otherwise access.
In decentralized JWT authentication, account resolvers are how servers obtain and cache account JWTs. They do not hold user JWTs: a user JWT is presented by the client when it connects and is not preloaded or stored on the server side.
The client presents its user JWT during connection. The server uses its configured trust chain and account information to validate that user JWT. This is what allows you to add clients without a server configuration update for each user.
It does not have to.
A traditional administrative flow may generate a full credentials file and deliver it to a user through a secure channel. That credentials file includes both the user JWT and the private seed needed to sign nonce challenges.
For a client-generated-key flow, the client can generate the private key locally and send only the public NKey to your onboarding service. The onboarding service builds a user JWT with that public key as its subject, signs it with an account signing key, and returns the JWT. The client then combines that JWT with its local private key material when connecting.
The important invariant is: the client must have access to the private key at connection time, and the server must be able to verify the nonce signature against the public key in the JWT.
nsc for this?nsc is useful for administrative workflows, especially when an operator or account administrator is creating users and distributing credentials.
For a dynamic onboarding service or auth callout-style integration, avoid shelling out to nsc as the core implementation mechanism. Use the NATS JWT or JWT builder libraries appropriate for your language/runtime so your service can issue user JWTs directly.
The standard nsc user-creation workflow generates the user key pair as part of creating the user. That is convenient for administrator-driven credential distribution, but it does not match a model where the private key must never leave the client. If keeping the private key exclusively on the client is a hard requirement, build user JWTs programmatically with the NATS JWT libraries, passing the client-supplied public key as the JWT subject and signing with an account signing key.
NATS auth callout can be useful when authentication and authorization need to be delegated to an external service. It works differently from the pre-issued flow above: instead of the client arriving with a user JWT it obtained earlier, the server invokes your callout service on each connection attempt, and your service returns the user JWT the server should apply for that connection. In this model, your service verifies the client using whatever identity proof your environment requires, then assigns the NATS identity and permissions that should apply.
For this use case, think of auth callout as an integration point for your onboarding or identity system, not as a reason to store every client in static server configuration. The callout service still needs to make careful decisions about identity proof, permissions, key handling, and token lifetime.
If you are exploring this path, the Synadia example repository synadia-io/callout.go is a useful reference point.
Authentication proves who the client is. Authorization determines what the client can do.
For distributed subscribers, your issuer should generate JWT claims that grant only the subjects the client should access. For example, rather than granting broad subscription rights to all subjects, issue permissions scoped to the specific subject hierarchy that client is allowed to consume.
The exact permission model depends on your application, but the pattern is the same:
A dynamic JWT onboarding flow removes the need to edit server config for every new user, but it does not remove the need for lifecycle management. Plan for:
Short-lived JWTs can simplify some operational problems, but they may require clients to refresh credentials more often. Longer-lived JWTs reduce refresh frequency, but make revocation and permission changes more important to design carefully.
For an unknown number of distributed NATS clients, prefer decentralized JWT authentication over per-user server configuration. Let each client generate its NKey pair locally, send only the public NKey to a trusted onboarding service, and have that service issue a signed user JWT with the right permissions.
On connect, NATS validates the trusted signer and challenges the client to sign a nonce. That nonce signature proves the client holds the private key corresponding to the public key in the JWT, while the JWT claims define what the client is allowed to do.
Want help from the NATS experts? Meet with our architects to get help tailored to your use case and environment.



News and content from across the community