Skip to content

Instantly share code, notes, and snippets.

@rayjanoka
Last active August 17, 2021 18:42
Show Gist options
  • Save rayjanoka/d48f2c55470098ec8945cb7a0f6d3224 to your computer and use it in GitHub Desktop.
Save rayjanoka/d48f2c55470098ec8945cb7a0f6d3224 to your computer and use it in GitHub Desktop.
Example code for the nats jwt in nodejs
const nkeys = require("nkeys.js");
const base32Encode = require('base32-encode');
const base64Url = require('base64url');
const shajs = require('sha.js');
const utf8 = require('utf8');
const path = require('path');
const fs = require("fs");
async function setupClaim(issuedAt, expiresAt, accountSigningKeyPub, jti, userPublicKey, accountId, pub, sub) {
return `{
"iat": ${issuedAt},
"exp": ${expiresAt},
"iss": "${accountSigningKeyPub}",
"jti": "${jti}",
"name": "${userPublicKey}",
"nats": {
"data": -1,
"issuer_account": "${accountId}",
"payload": -1,
"pub": ${JSON.stringify(pub)},
"sub": ${JSON.stringify(sub)},
"subs": -1,
"type": "user",
"version": 2
},
"sub": "${userPublicKey}"
}`
}
async function setupCreds(jwt, keyPair) {
return `-----BEGIN NATS USER JWT-----
${jwt}
------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-----
${new TextDecoder().decode(keyPair.getSeed())}
------END USER NKEY SEED------
*************************************************************
`
}
async function issueUserJWT(userPublicKey, n) {
const textEncoder = new TextEncoder;
const accountSigningSeed = global.accountSigningSeed;
const accountId = global.accountId;
const subject = global.subject
// use the supplied account signing key to create new users
const accountSigningKeyPair = nkeys.fromSeed(Buffer.from(accountSigningSeed));
// generate a new public key from our account signing seed (will become the user claim's iss)
const accountSigningKeyPub = accountSigningKeyPair.getPublicKey()
// set the time window
const issuedAt = Math.floor(new Date().getTime() / 1000)
const expiresAt = issuedAt + 86400 // seconds
const subjectPerms = { "allow": [subject] }
// Create a claim without the jti so we can hash it
let userClaim = await setupClaim(
issuedAt,
expiresAt,
accountSigningKeyPub,
"", // TBD
userPublicKey,
accountId,
subjectPerms,
subjectPerms
);
// Encode with utf-8 and minify
const encUserClaim = utf8.encode(JSON.stringify(JSON.parse(userClaim)))
// Create sha-256 digest of user claim
const userClaimDigest = shajs('sha256').update(encUserClaim).digest('hex')
// Encode the user claim digest base32 with RFC4648
const jti = base32Encode(textEncoder.encode(userClaimDigest), 'RFC4648', { padding: false });
// Rebuild the claim with the user claim digest (jti)
userClaim = await setupClaim(
issuedAt,
expiresAt,
accountSigningKeyPub,
jti,
userPublicKey,
accountId,
subjectPerms,
subjectPerms
)
if (n === 1) { console.log("New User Claim: " + userClaim); }
const header = `{"typ":"JWT","alg":"ed25519-nkey"}`;
// Encode all the things
const encHeader = base64Url(header, "utf8")
const encBody = base64Url(JSON.stringify(JSON.parse(userClaim)), "utf8")
// Sign the full message with the account signing key
const sigArray = textEncoder.encode(`${encHeader}.${encBody}`);
const encSig = base64Url(accountSigningKeyPair.sign(sigArray), "utf8")
// Concatenate the final JWT
return `${encHeader}.${encBody}.${encSig}`
}
async function issueUserCreds(n) {
// create a fresh key pair
const userKeyPair = nkeys.createUser();
const userPublicKey = userKeyPair.getPublicKey();
// console.log("New Public Key: " + userPublicKey)
const userJwt = await issueUserJWT(userPublicKey, n)
// console.log("New JWT: " + jwt)
const userCreds = await setupCreds(userJwt, userKeyPair)
return [userCreds, userPublicKey]
}
async function batchAddUser(n) {
for (let i = 0; i < n; i++) {
await issueUserCreds(n).then((data) => {
const userCreds = data[0];
const userPublicKey = data[1];
if (n === 1) {
console.log(`New NATS Credentials: \n${userCreds}`)
}
console.log(`${i.toLocaleString()}: Generated new creds: ${userPublicKey}`)
fs.writeFileSync(path.join(__dirname, 'gen/', `${userPublicKey}.creds`), userCreds)
})
}
console.log('complete')
}
if (!fs.existsSync(path.join(__dirname, 'gen/'))) {
fs.mkdirSync(path.join(__dirname, 'gen/'));
}
const args = process.argv.slice(2);
global.accountId = args[0]
global.accountSigningSeed = args[1]
global.subject = args[2]
batchAddUser(parseFloat(args[3]))
@rayjanoka
Copy link
Author

rayjanoka commented Aug 17, 2021

This was adapted from the NATS documentation's .NET example. I did not verify the safety of the libraries used.

package.json

{
  "name": "nats-jwt-nodejs",
  "version": "1.0.0",
  "dependencies": {
    "base32-encode": "^1.2.0",
    "base64url": "^3.0.1",
    "nkeys.js": "^1.0.0-9",
    "sha.js": "^2.4.11",
    "utf8": "^3.0.0"
  }
}

usage

nats-jwt-nodejs-add-user.js <Account ID> <Account Signing Seed> <Subject to Allow> <Number of Users to Create>

$ node nats-jwt-nodejs-add-user.js  ABKKJNVQL3Q4QZD2SG... SAAJLJRXJJ3FO2E7BJO... testSubject 1
Generated new creds: UA5M24HRSJ6Z354RHKWOWIYQ2VPBJL7AVDGYYC44MAX6HMF4AHTPPLNX
User Claim: {
    "iat": 1628133864,
    "exp": 1628220264,
    "iss": "AAYP4GGL3IQN6XE4OUYAA74LRKY6S2AMUCPF56IRTJU3RTZCJ3MD6O54",
    "jti": "G5RTOYJZG4YGKYZTMYZTCNRVHA4DCMLFG42TCZRTHE4GIZTEME4WEMZZMEYGCMRUME4GMOBVMEYWCYJYMIYTSNDFMNRDQMTEGM4DIMA",
    "name": "UA5M24HRSJ6Z354RHKWOWIYQ2VPBJL7AVDGYYC44MAX6HMF4AHTPPLNX",
    "nats": {
        "data": -1,
        "issuer_account": "ABKKJNVQL3Q4QZD2SGWJI26YEK35BJSQ7K63J6XVRYHV6IWIE5PDMISW",
        "payload": -1,
        "pub": {"allow":["testSubject"]},
        "sub": {"allow":["testSubject"]},
        "subs": -1,
        "type": "user",
        "version": 2
    },
    "sub": "UA5M24HRSJ6Z354RHKWOWIYQ2VPBJL7AVDGYYC44MAX6HMF4AHTPPLNX"
}

$ cat gen/UA5M24HRSJ6Z354RHKWOWIYQ2VPBJL7AVDGYYC44MAX6HMF4AHTPPLNX.creds 
-----BEGIN NATS USER JWT-----
eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJpYXQiOjE2MjgxMzM4NjQsImV4cCI6MTYyODIyMDI2NCwiaXNzIjoiQUFZUDRHR0wzSVFONlhFNE9VWUFBNzRMUktZNlMyQU1VQ1BGNTZJUlRKVTNSVFpDSjNNRDZPNTQiLCJqdGkiOiJHNVJUT1lKWkc0WUdLWVpUTVlaVENOUlZIQTREQ01MRkc0MlRDWlJUSEU0R0laVEVNRTRXRU1aWk1FWUdDTVJVTUU0R01PQlZNRVlXQ1lKWU1JWVRTTkRGTU5SRFFNVEVHTTRESU1BIiwibmFtZSI6IlVBNU0yNEhSU0o2WjM1NFJIS1dPV0lZUTJWUEJKTDdBVkRHWVlDNDRNQVg2SE1GNEFIVFBQTE5YIiwibmF0cyI6eyJkYXRhIjotMSwiaXNzdWVyX2FjY291bnQiOiJBQktLSk5WUUwzUTRRWkQyU0dXSkkyNllFSzM1QkpTUTdLNjNKNlhWUllIVjZJV0lFNVBETUlTVyIsInBheWxvYWQiOi0xLCJwdWIiOnsiYWxsb3ciOlsidGVzdFN1YmplY3QiXX0sInN1YiI6eyJhbGxvdyI6WyJ0ZXN0U3ViamVjdCJdfSwic3VicyI6LTEsInR5cGUiOiJ1c2VyIiwidmVyc2lvbiI6Mn0sInN1YiI6IlVBNU0yNEhSU0o2WjM1NFJIS1dPV0lZUTJWUEJKTDdBVkRHWVlDNDRNQVg2SE1GNEFIVFBQTE5YIn0.7WGN_FwVVxtvqEwg9hWFv4ZlhGFqPvk_g5dFD9vGiaBxolUers0-IBbzYcZYizSN86xyLGcxiaQQxGuUXdsPDQ
------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-----
SUAE5KE7WBUBRG3CTYX6CXZHQICMII4YF46E3OMIAFVPLQLKHKJDWTMMAU
------END USER NKEY SEED------

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment