github.com/nats-io/jwt/v2@v2.5.6/example_test.go (about) 1 package jwt 2 3 import ( 4 "fmt" 5 "os" 6 "path" 7 "testing" 8 9 jwt "github.com/nats-io/jwt/v2/v1compat" 10 "github.com/nats-io/nkeys" 11 ) 12 13 func TestExample(t *testing.T) { 14 // create an operator key pair (private key) 15 okp, err := nkeys.CreateOperator() 16 if err != nil { 17 t.Fatal(err) 18 } 19 // extract the public key 20 opk, err := okp.PublicKey() 21 if err != nil { 22 t.Fatal(err) 23 } 24 25 // create an operator claim using the public key for the identifier 26 oc := jwt.NewOperatorClaims(opk) 27 oc.Name = "O" 28 // add an operator signing key to sign accounts 29 oskp, err := nkeys.CreateOperator() 30 if err != nil { 31 t.Fatal(err) 32 } 33 // get the public key for the signing key 34 ospk, err := oskp.PublicKey() 35 if err != nil { 36 t.Fatal(err) 37 } 38 // add the signing key to the operator - this makes any account 39 // issued by the signing key to be valid for the operator 40 oc.SigningKeys.Add(ospk) 41 42 // self-sign the operator JWT - the operator trusts itself 43 operatorJWT, err := oc.Encode(okp) 44 if err != nil { 45 t.Fatal(err) 46 } 47 48 // create an account keypair 49 akp, err := nkeys.CreateAccount() 50 if err != nil { 51 t.Fatal(err) 52 } 53 // extract the public key for the account 54 apk, err := akp.PublicKey() 55 if err != nil { 56 t.Fatal(err) 57 } 58 // create the claim for the account using the public key of the account 59 ac := jwt.NewAccountClaims(apk) 60 ac.Name = "A" 61 // create a signing key that we can use for issuing users 62 askp, err := nkeys.CreateAccount() 63 if err != nil { 64 t.Fatal(err) 65 } 66 // extract the public key 67 aspk, err := askp.PublicKey() 68 if err != nil { 69 t.Fatal(err) 70 } 71 // add the signing key (public) to the account 72 ac.SigningKeys.Add(aspk) 73 74 // now we could encode an issue the account using the operator 75 // key that we generated above, but this will illustrate that 76 // the account could be self-signed, and given to the operator 77 // who can then re-sign it 78 accountJWT, err := ac.Encode(akp) 79 if err != nil { 80 t.Fatal(err) 81 } 82 83 // the operator would decode the provided token, if the token 84 // is not self-signed or signed by an operator or tampered with 85 // the decoding would fail 86 ac, err = jwt.DecodeAccountClaims(accountJWT) 87 if err != nil { 88 t.Fatal(err) 89 } 90 // here the operator is going to use its private signing key to 91 // re-issue the account 92 accountJWT, err = ac.Encode(oskp) 93 if err != nil { 94 t.Fatal(err) 95 } 96 97 // now back to the account, the account can issue users 98 // need not be known to the operator - the users are trusted 99 // because they will be signed by the account. The server will 100 // look up the account get a list of keys the account has and 101 // verify that the user was issued by one of those keys 102 ukp, err := nkeys.CreateUser() 103 if err != nil { 104 t.Fatal(err) 105 } 106 upk, err := ukp.PublicKey() 107 if err != nil { 108 t.Fatal(err) 109 } 110 uc := jwt.NewUserClaims(upk) 111 // since the jwt will be issued by a signing key, the issuer account 112 // must be set to the public ID of the account 113 uc.IssuerAccount = apk 114 userJwt, err := uc.Encode(askp) 115 if err != nil { 116 t.Fatal(err) 117 } 118 // the seed is a version of the keypair that is stored as text 119 useed, err := ukp.Seed() 120 if err != nil { 121 t.Fatal(err) 122 } 123 // generate a creds formatted file that can be used by a NATS client 124 creds, err := jwt.FormatUserConfig(userJwt, useed) 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 // now we are going to put it together into something that can be run 130 // we create a directory to store the server configuration, the creds 131 // file and a small go program that uses the creds file 132 dir, err := os.MkdirTemp(os.TempDir(), "jwt_example") 133 if err != nil { 134 t.Fatal(err) 135 } 136 // print where we generated the file 137 t.Logf("generated example %s", dir) 138 t.Log("to run this example:") 139 t.Logf("> cd %s", dir) 140 t.Log("> go mod init example") 141 t.Log("> go mod tidy") 142 t.Logf("> nats-server -c %s/resolver.conf &", dir) 143 t.Log("> go run main.go") 144 145 // we are generating a memory resolver server configuration 146 // it lists the operator and all account jwts the server should 147 // know about 148 resolver := fmt.Sprintf(`operator: %s 149 150 resolver: MEMORY 151 resolver_preload: { 152 %s: %s 153 } 154 `, operatorJWT, apk, accountJWT) 155 if err := os.WriteFile(path.Join(dir, "resolver.conf"), 156 []byte(resolver), 0644); err != nil { 157 t.Fatal(err) 158 } 159 160 // store the creds 161 credsPath := path.Join(dir, "u.creds") 162 if err := os.WriteFile(credsPath, creds, 0644); err != nil { 163 t.Fatal(err) 164 } 165 166 // here we generate as small go program that connects using the creds file 167 // subscribes, and publishes a message 168 connect := fmt.Sprintf(` 169 package main 170 171 import ( 172 "fmt" 173 "sync" 174 175 "github.com/nats-io/nats.go" 176 ) 177 178 func main() { 179 var wg sync.WaitGroup 180 wg.Add(1) 181 nc, err := nats.Connect(nats.DefaultURL, nats.UserCredentials(%q)) 182 if err != nil { 183 panic(err) 184 } 185 nc.Subscribe("hello.world", func(m *nats.Msg) { 186 fmt.Println(m.Subject) 187 wg.Done() 188 }) 189 nc.Publish("hello.world", []byte("hello")) 190 nc.Flush() 191 wg.Wait() 192 nc.Close() 193 } 194 195 `, credsPath) 196 if err := os.WriteFile(path.Join(dir, "main.go"), []byte(connect), 0644); err != nil { 197 t.Fatal(err) 198 } 199 }