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# C#2 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

Code

package main


import (
	"fmt"
	"log"


	"github.com/nats-io/jwt/v2"
	"github.com/nats-io/nkeys"
)


func main() {
	log.SetFlags(0)

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://127.0.0.1:4222

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 https://jwt.io, 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.>")
	userClaims.Permissions.Sub.Allow.Add("_INBOX.>")


	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)
}

Output

operator pubkey: OCCKR76QCKV4R224WP6ZISXWLXLJZWDF22TRZFQM2I6KUPEDQ3OVCJ6N
operator seed: SOALU7LPGJK2BDF7IHD7UZT6ZM23UMKYLGJLNN35QJSUI5BNR4DJRFH4R4

account pubkey: AB5D6N64ZGUTCGETBW3HSORLTJH5UCCB5CKPZFWCF6UV3KI5BCTRPFDC
account seed: SAALXUEDN2QR5KZDDSH5S4RIWAZDM7CVDG5HNJI2HS5LBVYFTLAQCOXZAU
account claims: {
  "name": "my-account",
  "sub": "AB5D6N64ZGUTCGETBW3HSORLTJH5UCCB5CKPZFWCF6UV3KI5BCTRPFDC",
  "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

user pubkey: UATWOEX5T5LGWJ54SC7H762G5PKSSFPVTJX67IUFENTVPYHA4DPDJUZU
user seed: SUALJTG5JNRQCQKFE652DV4XID522ALOHJNQVHKKDJNVGWHCLHOEXEROEM
userclaims: {
  "name": "my-user",
  "sub": "UATWOEX5T5LGWJ54SC7H762G5PKSSFPVTJX67IUFENTVPYHA4DPDJUZU",
  "nats": {
    "pub": {
      "allow": [
        "foo.\u003e",
        "bar.\u003e"
      ]
    },
    "sub": {
      "allow": [
        "_INBOX.\u003e"
      ]
    },
    "subs": -1,
    "data": 1073741824,
    "payload": -1
  }
}
user jwt: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJPSlpVRE1WR0M1V09XTkVWRkpPRUJRRzdDR05WUlNFWlVQTTdLVFJMRTI2VFg3T1BIM1RRIiwiaWF0IjoxNjc4OTczOTQ1LCJpc3MiOiJBQjVENk42NFpHVVRDR0VUQlczSFNPUkxUSkg1VUNDQjVDS1BaRldDRjZVVjNLSTVCQ1RSUEZEQyIsIm5hbWUiOiJteS11c2VyIiwic3ViIjoiVUFUV09FWDVUNUxHV0o1NFNDN0g3NjJHNVBLU1NGUFZUSlg2N0lVRkVOVFZQWUhBNERQREpVWlUiLCJuYXRzIjp7InB1YiI6eyJhbGxvdyI6WyJmb28uXHUwMDNlIiwiYmFyLlx1MDAzZSJdfSwic3ViIjp7ImFsbG93IjpbIl9JTkJPWC5cdTAwM2UiXX0sInN1YnMiOi0xLCJkYXRhIjoxMDczNzQxODI0LCJwYXlsb2FkIjotMSwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfX0.PYT1aJJXiJd9Jb5b1m03jBs64GJzjKRtLOH4hoKJ8v8MKk13nhzGFtKcIKn2vg00uYBYlOkWPgzJ6hYuKr0ECA
creds file: -----BEGIN NATS USER JWT-----
eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJPSlpVRE1WR0M1V09XTkVWRkpPRUJRRzdDR05WUlNFWlVQTTdLVFJMRTI2VFg3T1BIM1RRIiwiaWF0IjoxNjc4OTczOTQ1LCJpc3MiOiJBQjVENk42NFpHVVRDR0VUQlczSFNPUkxUSkg1VUNDQjVDS1BaRldDRjZVVjNLSTVCQ1RSUEZEQyIsIm5hbWUiOiJteS11c2VyIiwic3ViIjoiVUFUV09FWDVUNUxHV0o1NFNDN0g3NjJHNVBLU1NGUFZUSlg2N0lVRkVOVFZQWUhBNERQREpVWlUiLCJuYXRzIjp7InB1YiI6eyJhbGxvdyI6WyJmb28uXHUwMDNlIiwiYmFyLlx1MDAzZSJdfSwic3ViIjp7ImFsbG93IjpbIl9JTkJPWC5cdTAwM2UiXX0sInN1YnMiOi0xLCJkYXRhIjoxMDczNzQxODI0LCJwYXlsb2FkIjotMSwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfX0.PYT1aJJXiJd9Jb5b1m03jBs64GJzjKRtLOH4hoKJ8v8MKk13nhzGFtKcIKn2vg00uYBYlOkWPgzJ6hYuKr0ECA
------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.

-----BEGIN USER NKEY SEED-----
SUALJTG5JNRQCQKFE652DV4XID522ALOHJNQVHKKDJNVGWHCLHOEXEROEM
------END USER NKEY SEED------

*************************************************************

Recording

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