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.
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------ *************************************************************