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