NATS Logo by Example

Private Inbox using JWT in Authentication and Authorization

This is the analog to the standard Private Inbox example, but leveraging the NKeys/JWT authentication method.

Replies to a request using the built-in request-reply pattern all share the same subject pattern _INBOX.>. For multi-user applications where isolation of messages within the same account is required, an explicit inbox prefix can be used when a client connects to the server. This combined with explicit permissions prevents users from snooping on service replies.

CLI Go Python Deno Node Rust C# Java Ruby Elixir C
Jump to the output or the recording
$ nbe run auth/private-inbox-jwt/cli
View the source code or learn how to run this example yourself

Code

#!/bin/sh


set -uo pipefail


NATS_URL="${NATS_URL:-nats://localhost:4222}"

Create the operator, generate a signing key (which is a best practice), and initialize the default SYS account and sys user.

nsc add operator --generate-signing-key --sys --name local

A follow-up edit of the operator enforces signing keys are used for accounts as well. Setting the server URL is a convenience so that it does not need to be specified with call nsc push.

nsc edit operator --require-signing-keys \
  --account-jwt-server-url "$NATS_URL"

Next we need to create an account intended for application usage. It is currently a two-step process to create the account, followed by generating the signing key.

nsc add account APP
nsc edit account APP --sk generate

This command generates the bit of configuration to be used by the server to setup the embedded JWT resolver.

nsc generate config --nats-resolver --sys-account SYS > resolver.conf

Create the most basic config that simply include the generated resolver config.

cat <<- EOF > server.conf
include resolver.conf
EOF

Start the server.

nats-server -c server.conf 2> /dev/null &
SERVER_PID=$!


sleep 1

Push the account up to the server.

nsc push -a APP

Next we will create three users. The first one called greeter will be used for the greet service. It can subscribe to a dedicated subject in order to service requests.

nsc add user --account APP greeter \
  --allow-sub 'services.greet' \
  --allow-pub-response

The next two users emulate consumers of the service. They can publish on their own prefixed subject as well as publish to services scoped the their respective name.

nsc add user --account APP joe \
  --allow-pub 'joe.>' \
  --allow-pub 'services.*'


nsc add user --account APP pam \
  --allow-pub 'pam.>' \
  --allow-pub 'services.*'

Since we didn’t set an explicit allow for subscriptions, these users can still subscribe to > which means joe could subscribe to pam.> and vice versa. We do know that in order to receive replies from services, we need to subscribe to _INBOX.>.

nsc edit user --account APP joe \
  --allow-sub '_INBOX.>'


nsc edit user --account APP pam \
  --allow-sub '_INBOX.>'

A nice side effect of this is that now, joe and pam can’t subscribe to each other’s subjects, however, what about _INBOX.>? Let’s observe the current behavior and then see how we can address this.

First, let’s save a few contexts for easier reference.

nats context save greeter \
  --nsc nsc://local/APP/greeter


nats context save joe \
  --nsc nsc://local/APP/joe


nats context save pam \
  --nsc nsc://local/APP/pam

Then we startup the greeter service that simply returns a unique reply ID.

nats --context greeter \
  reply 'services.greet' \
  'Reply {{ID}}' &


GREETER_PID=$!

Tiny sleep to ensure the service is connected.

sleep 0.5

Send a greet request from joe and pam.

nats --context joe request 'services.greet' ''
nats --context pam request 'services.greet' ''

But can pam also receive replies from requests sent by joe? Indeed, by subscribing to the inbox.

nats --context pam sub '_INBOX.>' &
INBOX_SUB_PID=$!

When joe sends a request, the reply will come back to him, but also be received by pam. 🤨 This is actually expected and generally fine since accounts are expected to be the isolation boundary, at a certain level of scale, creating users with granular permissions becomes increasingly necessary.

nats --context joe request 'services.greet' ''

Sinces inboxes are randomly generated by the server, by default we can’t pin down the specific set of subjects to provide permission to. However, as a client, there is the option of defining an explicit inbox prefix other than _INBOX.

nats --context joe --inbox-prefix _INBOX_joe request 'services.greet' ''

Now that we can have a differentiated inbox prefix, we can deny the default one and allow for the custom one.

nsc edit user --account APP joe \
  --deny-sub '_INBOX.>' \
  --allow-sub '_INBOX_joe.>'


nsc edit user --account APP pam \
  --deny-sub '_INBOX.>' \
  --allow-sub '_INBOX_pam.>'

Stop the previous service to pick up the new permission. Now pam cannot subscribe to the general _INBOX nor joe’s specific one since it does not have an explicit allow.

kill $INBOX_SUB_PID
nats --context pam sub '_INBOX.>'
nats --context pam sub '_INBOX_joe.>'

Now we can send requests and receive replies in isolation.

nats --context joe --inbox-prefix _INBOX_joe request 'services.greet' ''
nats --context pam --inbox-prefix _INBOX_pam request 'services.greet' ''

Finally stop the service and server.

kill $GREETER_PID
kill $SERVER_PID

Output

Network b7bfbec0_default  Creating
Network b7bfbec0_default  Created
             _             _               
 _ __   __ _| |_ ___      | |__   _____  __
| '_ \ / _` | __/ __|_____| '_ \ / _ \ \/ /
| | | | (_| | |_\__ \_____| |_) | (_) >  < 
|_| |_|\__,_|\__|___/     |_.__/ \___/_/\_\
                                           
nats-box v0.12.0
[ OK ] generated and stored operator key "ODFMSKLO5IXEZEYGRURAIXESSKTOITJY6J5ENUZM2DOPODARGAMRCBYE"
[ OK ] added operator "local"
[ OK ] When running your own nats-server, make sure they run at least version 2.2.0
[ OK ] created operator signing key: OBVV4SZH4LGW2DTKN75CFDKPI3EAPG2JY55R3YOOKYPVOPTW6QLLKRSV
[ OK ] created system_account: name:SYS id:ABBGTNCV4YW7PFBT6J5DCT4E7MFJPINFPAKZKYX3YEDOBD2B2CKJNYJI
[ OK ] created system account user: name:sys id:UAX4G37MYJSFKJZAYW4DCBPZNWYUQSFHEJTJVDICYXTSFWFU4OSM76RD
[ OK ] system account user creds file stored in `/nsc/nkeys/creds/local/SYS/sys.creds`
[ OK ] strict signing key usage set to: true
[ OK ] set account jwt server url to "nats://localhost:4222"
[ OK ] edited operator "local"
[ OK ] generated and stored account key "AB5MLX47CDR6HLNI7CGRRAOPHZXHA5WLLZBCM3Y5M5ITSBZWT66HBDUZ"
[ OK ] added account "APP"
[ OK ] added signing key "AAT6RZPFDH5Y4HRDO554REFLEZXC7OC6YLI6ED3SCYNFZ4NDMYY7WB65"
[ OK ] edited account "APP"
[ OK ] push to nats-server "nats://localhost:4222" using system account "SYS":
       [ OK ] push APP to nats-server with nats account resolver:
              [ OK ] pushed "APP" to nats-server NCXGMGNQBBKCTS3LO2SLVRCZP7CJGO6GWSR6KADR4Y3D6LG7TQS2S6AB: jwt updated
              [ OK ] pushed to a total of 1 nats-server
[ OK ] set max responses to 1
[ OK ] added sub "services.greet"
[ OK ] generated and stored user key "UBAK6JLWQMHXEJ4WCFGVRKZYCBHLCNI6JGFHFMUODPNL27ZHOYOCHOO3"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/greeter.creds`
[ OK ] added user "greeter" to account "APP"
[ OK ] added pub pub "joe.>"
[ OK ] added pub pub "services.*"
[ OK ] generated and stored user key "UARFKBJRTAZM4OW3WZIPOXAV5BCS3JH6BCCUARVFHJDFJ7IY2HXJUXLJ"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/joe.creds`
[ OK ] added user "joe" to account "APP"
[ OK ] added pub pub "pam.>"
[ OK ] added pub pub "services.*"
[ OK ] generated and stored user key "UBYSMJ3Y366XN5H6JP5IHNQ3M76CTXK2BRXSTK2C75HY7LFRUASGT3OV"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/pam.creds`
[ OK ] added user "pam" to account "APP"
[ OK ] added sub "_INBOX.>"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/joe.creds`
[ OK ] edited user "joe"
[ OK ] added sub "_INBOX.>"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/pam.creds`
[ OK ] edited user "pam"
NATS Configuration Context "greeter"

      Server URLs: nats://127.0.0.1:4222
      Credentials: /nsc/nkeys/creds/local/APP/greeter.creds (OK)
       NSC Lookup: nsc://local/APP/greeter
             Path: /nsc/.config/nats/context/greeter.json

NATS Configuration Context "joe"

      Server URLs: nats://127.0.0.1:4222
      Credentials: /nsc/nkeys/creds/local/APP/joe.creds (OK)
       NSC Lookup: nsc://local/APP/joe
             Path: /nsc/.config/nats/context/joe.json

NATS Configuration Context "pam"

      Server URLs: nats://127.0.0.1:4222
      Credentials: /nsc/nkeys/creds/local/APP/pam.creds (OK)
       NSC Lookup: nsc://local/APP/pam
             Path: /nsc/.config/nats/context/pam.json

17:21:11 Listening on "services.greet" in group "NATS-RPLY-22"
17:21:11 Sending request on "services.greet"


17:21:11 [#0] Received on subject "services.greet":
17:21:11 Received with rtt 1.421611ms
Reply EiaIxD2O9zht77dHYbTZfN

17:21:11 Sending request on "services.greet"
17:21:11 [#1] Received on subject "services.greet":


17:21:11 Received with rtt 1.089909ms
Reply EiaIxD2O9zht77dHYbTZk4

17:21:12 Subscribing on _INBOX.>
17:21:12 Sending request on "services.greet"
17:21:12 [#2] Received on subject "services.greet":


Reply EiaIxD2O9zht77dHYbTZol

17:21:12 Received with rtt 681.706µs
[#1] Received on "_INBOX.ZYAAExoXcyDKsFDdxuFgQb.PIt1MPL3"
Reply EiaIxD2O9zht77dHYbTZol

17:21:12 Sending request on "services.greet"
17:21:12 [#3] Received on subject "services.greet":
17:21:12 Unexpected NATS error from server nats://127.0.0.1:4222: nats: Permissions Violation for Subscription to "_INBOX_joe.fNg5JF7k4bT2VSUa4Ep6wD.7tIj5Fxs"


[ OK ] added sub "_INBOX_joe.>"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/joe.creds`
[ OK ] edited user "joe"
[ OK ] added sub "_INBOX_pam.>"
[ OK ] generated user creds file `/nsc/nkeys/creds/local/APP/pam.creds`
[ OK ] edited user "pam"
17:21:17 Subscribing on _INBOX.>
17:21:17 Unexpected NATS error from server nats://127.0.0.1:4222: nats: Permissions Violation for Subscription to "_INBOX.>"
nats: error: nats: Permissions Violation for Subscription to "_INBOX.>", try --help
17:21:17 Subscribing on _INBOX_joe.>
nats: error: nats: Permissions Violation for Subscription to "_INBOX_joe.>", try --help
17:21:17 Sending request on "services.greet"


17:21:17 [#4] Received on subject "services.greet":
17:21:17 Received with rtt 1.093234ms
Reply EiaIxD2O9zht77dHYbTZy9

17:21:17 Sending request on "services.greet"
17:21:17 [#5] Received on subject "services.greet":


17:21:17 Received with rtt 2.893689ms
Reply EiaIxD2O9zht77dHYbTa2q

Recording

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