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 Deno Node Rust C# 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

Network e62f65ec_default  Creating
Network e62f65ec_default  Created
Container e62f65ec-nats-1  Creating
Container e62f65ec-nats-1  Created
Container e62f65ec-nats-1  Starting
Container e62f65ec-nats-1  Started
operator pubkey: OA3RVMES5FWOZXSB5RNK36VBC34GRZG6LZQMRUXUGMOZRDYJYFMEPUY7
operator seed: SOANKZ77PHTRUWRUZFVTCKQJDRJMNPL47FO2EO2PZEIRM5Z3LNE32KMHCM

account pubkey: ABNK2MJUJCCE3PYD2FA5WOS7L7A4DCFCG54XHQS2VWNCKBZASDHQO2L5
account seed: SAADJWVFWVMEAQNV2TAJS7ETWT4JQNLINW7QKKWUINRYFRXHMGEREJJX6U
account claims: {
  "name": "my-account",
  "sub": "ABNK2MJUJCCE3PYD2FA5WOS7L7A4DCFCG54XHQS2VWNCKBZASDHQO2L5",
  "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.eyJqdGkiOiJNNTMyWDZRTlc1MllFRkgyTjY1RFlGQk9HNFREVlVMVVRBNkk1SFZQUVg2WUlDWFhJTDZRIiwiaWF0IjoxNjYxODU4MTUwLCJpc3MiOiJPQTNSVk1FUzVGV09aWFNCNVJOSzM2VkJDMzRHUlpHNkxaUU1SVVhVR01PWlJEWUpZRk1FUFVZNyIsIm5hbWUiOiJteS1hY2NvdW50Iiwic3ViIjoiQUJOSzJNSlVKQ0NFM1BZRDJGQTVXT1M3TDdBNERDRkNHNTRYSFFTMlZXTkNLQlpBU0RIUU8yTDUiLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJpbXBvcnRzIjotMSwiZXhwb3J0cyI6LTEsIndpbGRjYXJkcyI6dHJ1ZSwiY29ubiI6LTEsImxlYWYiOi0xLCJtZW1fc3RvcmFnZSI6LTEsImRpc2tfc3RvcmFnZSI6LTF9LCJkZWZhdWx0X3Blcm1pc3Npb25zIjp7InB1YiI6e30sInN1YiI6e319LCJ0eXBlIjoiYWNjb3VudCIsInZlcnNpb24iOjJ9fQ.q_C-AlvT6QrhbfdoK7TViplpRpwDNlENSremSd91Lj2mB0DMuYAyu57vK9cpAAHXL0ct41Kbro8fLk4Ny9jHCw

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

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

Recording

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