NATS Logo by Example

Programmatic NKeys and JWTs in Authentication and Authorization

The primary (and recommended) way to create and manage accounts and users is using the nsc command-line tool. However, in some applications and use cases, it may be desirable to programmatically create accounts or users on-demand as part of an application-level account/user workflow rather than out-of-band on the command line (however, shelling out to nsc from your program is another option).

This example shows how to programmatically generate NKeys and JWTs. This can be used as an alternative or, more likely, in conjunction with the nsc tool for creating and managing accounts and users.

Note, not all languages currently implement standalone NKeys or JWT libraries.

CLI Go Python JavaScript Rust C# .NET V2 Java Ruby Elixir Crystal C
Jump to the output or the recording
$ nbe run auth/nkeys-jwts/go
View the source code or learn how to run this example yourself


package main

import (


func main() {

Create a one-off operator keypair for the purpose of this example. In practice, the operator needs to be created ahead of time to configure the resolver in the server config if you are deploying your own NATS server. This most commonly done using the “nsc” tool:

nsc add operator --generate-signing-key --sys --name local
nsc edit operator --require-signing-keys --account-jwt-server-url nats://

Signing keys are technically optional, but a best practice.

	operatorKP, _ := nkeys.CreateOperator()

We can distinguish operators, accounts, and users by the first character of their public key: O, A, or U.

	operatorPub, _ := operatorKP.PublicKey()
	fmt.Printf("operator pubkey: %s\n", operatorPub)

Seed values (private key), are prefixed with S.

	operatorSeed, _ := operatorKP.Seed()
	fmt.Printf("operator seed: %s\n\n", string(operatorSeed))

To create accounts on demand, we start with creatinng a new keypair which has a unique ID.

	accountKP, _ := nkeys.CreateAccount()

	accountPub, _ := accountKP.PublicKey()
	fmt.Printf("account pubkey: %s\n", accountPub)

	accountSeed, _ := accountKP.Seed()
	fmt.Printf("account seed: %s\n", string(accountSeed))

Create a new set of account claims and configure as desired including a readable name, JetStream limits, imports/exports, etc.

	accountClaims := jwt.NewAccountClaims(accountPub)
	accountClaims.Name = "my-account"

The only requirement to “enable” JetStream is setting the disk and memory limits to anything other than zero. -1 indicates “unlimited”.

	accountClaims.Limits.JetStreamLimits.DiskStorage = -1
	accountClaims.Limits.JetStreamLimits.MemoryStorage = -1

Inspecting the claims, you will notice the sub field is the public key of the account.

	fmt.Printf("account claims: %s\n", accountClaims)

Now we can sign the claims with the operator and encode it to a JWT string. To activate this account, it must be pushed up to the server using a client connection authenticated as the SYS account user:

nc.Request("$SYS.REQ.CLAIMS.UPDATE", []byte(accountJWT), time.Second)

If you copy the JWT output to, you will notice the iss field is set to the operator public key.

	accountJWT, _ := accountClaims.Encode(operatorKP)
	fmt.Printf("account jwt: %s\n\n", accountJWT)

It is important to call out that the nsc tool handles storage and management of operators, accounts, users. It writes out each nkey and JWT to a file and organizes everything for you. If you opt to create accounts or users dynamically, keep in mind you need to store and manage the keypairs and JWTs yourself.

If we want to create a user, the process is essentially the same as it was for the account.

	userKP, _ := nkeys.CreateUser()

	userPub, _ := userKP.PublicKey()
	fmt.Printf("user pubkey: %s\n", userPub)

	userSeed, _ := userKP.Seed()
	fmt.Printf("user seed: %s\n", string(userSeed))

Create the user claims, set the name, and configure permissions, expiry time, limits, etc.

	userClaims := jwt.NewUserClaims(userPub)
	userClaims.Name = "my-user"

	userClaims.Limits.Data = 1024 * 1024 * 1024

	userClaims.Permissions.Pub.Allow.Add("foo.>", "bar.>")

	fmt.Printf("userclaims: %s\n", userClaims)

Sign and encode the claims as a JWT.

	userJWT, _ := userClaims.Encode(accountKP)
	fmt.Printf("user jwt: %s\n", userJWT)

Produce the decorated credentials that can be written to a file and used by connecting clients.

	creds, _ := jwt.FormatUserConfig(userJWT, userSeed)
	fmt.Printf("creds file: %s\n", creds)



account claims: {
  "name": "my-account",
  "nats": {
    "limits": {
      "subs": -1,
      "data": -1,
      "payload": -1,
      "imports": -1,
      "exports": -1,
      "wildcards": true,
      "conn": -1,
      "leaf": -1,
      "mem_storage": -1,
      "disk_storage": -1
    "default_permissions": {
      "pub": {},
      "sub": {}
account jwt: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJQQkZFUzMzR0dJRlpNNlVHQzdOWTVBUkhSQkZWRlU0VUQ3RlMyV05MWkgzS1BHV0ZWRUZRIiwiaWF0IjoxNjc4OTczOTQ1LCJpc3MiOiJPQ0NLUjc2UUNLVjRSMjI0V1A2WklTWFdMWExKWldERjIyVFJaRlFNMkk2S1VQRURRM09WQ0o2TiIsIm5hbWUiOiJteS1hY2NvdW50Iiwic3ViIjoiQUI1RDZONjRaR1VUQ0dFVEJXM0hTT1JMVEpINVVDQ0I1Q0tQWkZXQ0Y2VVYzS0k1QkNUUlBGREMiLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJpbXBvcnRzIjotMSwiZXhwb3J0cyI6LTEsIndpbGRjYXJkcyI6dHJ1ZSwiY29ubiI6LTEsImxlYWYiOi0xLCJtZW1fc3RvcmFnZSI6LTEsImRpc2tfc3RvcmFnZSI6LTF9LCJkZWZhdWx0X3Blcm1pc3Npb25zIjp7InB1YiI6e30sInN1YiI6e319LCJ0eXBlIjoiYWNjb3VudCIsInZlcnNpb24iOjJ9fQ.4-kUapoPK_9A9L_CfJRBEe1XukgVBGaSU3J5tBFbajF3G5660BORa2CRUnN6x0dv-jUgui5EIQeANDB5kh_wDw

userclaims: {
  "name": "my-user",
  "nats": {
    "pub": {
      "allow": [
    "sub": {
      "allow": [
    "subs": -1,
    "data": 1073741824,
    "payload": -1
creds file: -----BEGIN NATS USER JWT-----
------END NATS USER JWT------

************************* IMPORTANT *************************
NKEY Seed printed below can be used to sign and prove identity.
NKEYs are sensitive and should be treated as secrets.

------END USER NKEY SEED------



Note, playback is half speed to make it a bit easier to follow.