NKeys are NATS’s cryptographic identity system, and they’re not just for signing messages; they actually form the basis of NATS’s entire decentralized authentication and authorization model.

Let’s see NKeys in action with a simple NATS client and server.

First, we need to generate an NKey. This is done using the nkey tool, which you’d typically install alongside NATS.

nkey --generate --seed

This command spits out two things: a public key (starting with NK...) and a secret seed (starting with SU...). The secret seed is what you use to generate the public key, and it’s also used for signing. Keep that seed very secret.

Now, let’s set up a basic NATS server (nats-server) to use these NKeys. We’ll create a simple configuration file, nats.conf:

listen: 4222
auth_timeout: 5
jetstream: { enabled: true }

# This is where the magic happens for NKeys
authorization {
  type: nkey
  # Point to the directory containing your NKey public key files
  # Each file should be named after the public key, e.g., NK...
  # The content of the file is the public key itself.
  # For simplicity, we'll just put the public key directly here for now.
  # In a real-world scenario, you'd have a directory of keys.
  # Example:
  # nkey_dir: "/etc/nats/nkeys"
  # For this example, we'll define a specific user.
  # The "user" here is the NKey public key.
  # The "permissions" define what this user can do.
  users = [
    {
      user: "NK... (your actual public key here)",
      permissions: {
        publish: { allow_subscriptions: [ "public.>" ] },
        subscribe: { allow_subscriptions: [ "public.>" ] }
      }
    }
  ]
}

Replace "NK... (your actual public key here)" with the public key you generated earlier.

With the server configured, we can start it:

nats-server -c nats.conf

Now, let’s write a simple Go client that uses the NKey seed to authenticate. We’ll need the nats.go client library.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/nats-io/nats.go"
)

func main() {
	// Your NKey seed generated earlier
	seed := "SU... (your actual secret seed here)"

	// Connect to the NATS server using the NKey seed
	nc, err := nats.Connect(nats.DefaultURL, nats.Nkey(seed))
	if err != nil {
		log.Fatalf("Error connecting to NATS: %v", err)
	}
	defer nc.Close()

	fmt.Printf("Connected to NATS at %s using NKey\n", nc.ConnectedUrl())

	// Publish a message
	subject := "public.hello"
	message := []byte("Hello from NKey authenticated client!")
	err = nc.Publish(subject, message)
	if err != nil {
		log.Fatalf("Error publishing: %v", err)
	}
	nc.Flush()
	fmt.Printf("Published message to %s\n", subject)

	// Subscribe to a subject
	sub, err := nc.SubscribeSync("public.>")
	if err != nil {
		log.Fatalf("Error subscribing: %v", err)
	}
	fmt.Printf("Subscribed to %s\n", sub.Subject)

	// Wait for a message (optional, for demonstration)
	msg, err := sub.NextMsg(10 * time.Second)
	if err != nil {
		log.Printf("No message received: %v", err)
	} else {
		fmt.Printf("Received message on %s: %s\n", msg.Subject, string(msg.Data))
	}
}

Replace "SU... (your actual secret seed here)" with the secret seed you generated.

Run this Go program. If everything is configured correctly, it will connect, publish, and potentially receive messages. The server verifies the client’s identity using the NKey seed provided, and then checks the authorization section of its configuration to see if that NKey is allowed to perform the requested operations.

The core problem NKeys solve is moving away from shared secrets (like username/password or static tokens) towards a public-key cryptography model. Each entity (user, service, application) generates its own key pair. The public key is shared and used for identification and authorization rules on the server. The private key (the seed) is kept secret and used by the client to prove its identity by signing connection requests and messages. This allows for fine-grained control without needing a central authority to issue credentials, and it makes revoking access as simple as removing a public key from the server’s configuration.

What most people don’t realize is that NKeys can also be used to derive multiple distinct identities from a single seed. The seed isn’t just a secret for one key; it’s a seed for a hierarchical key derivation function. You can use the same seed to generate keys for different services or roles, each with its own public key and associated permissions, all managed by the same underlying secret. This is done using the nkey tool with specific derivation paths, allowing a single secret to manage a complex set of decentralized identities.

The next step in understanding NATS security is exploring how these NKeys integrate with NATS JetStream for stream and consumer authorization.

Want structured learning?

Take the full Nats course →