NATS Logo by Example

Private Inbox in Authentication and Authorization

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/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}"

For this example, 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. The other 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. The _INBOX.> subscription permission is required in order to receive replies from a requester. A nice side effect of this is that now, joe and pam can’t subscribe to each other’s subjects (since an explicit allow has been declared), however, what about _INBOX.>? Let’s observe the current behavior and then see how we can address this.

cat <<- EOF > server.conf
accounts: {
  APP: {
    users: [
      {
        user: greeter,
        password: greeter,
        permissions: {
          sub: {
            allow: ["services.greet"]
          },
          allow_responses: true
        }
      },
      {
        user: joe,
        password: joe,
        permissions: {
          pub: {
            allow: ["joe.>", "services.*"]
          },
          sub: {
            allow: ["_INBOX.>"]
          },
        }
      },
      {
        user: pam,
        password: pam,
        permissions: {
          pub: {
            allow: ["pam.>", "services.*"]
          },
          sub: {
            allow: ["_INBOX.>"]
          },
        }
      },
    ]
  }
}
EOF

Start the server with the config.

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

Save a few contexts for convenience…

nats context save greeter \
  --user greeter --password greeter


nats context save joe \
  --user joe --password joe


nats context save pam \
  --user pam --password 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 set the only allow to be the one specific to the user.

cat <<- EOF > server.conf
accounts: {
  APP: {
    users: [
      {
        user: greeter,
        password: greeter,
        permissions: {
          sub: {
            allow: ["services.greet"]
          },
          allow_responses: true
        }
      },
      {
        user: joe,
        password: joe,
        permissions: {
          pub: {
            allow: ["joe.>", "services.*"]
          },
          sub: {
            allow: ["_INBOX_joe.>"]
          },
        }
      },
      {
        user: pam,
        password: pam,
        permissions: {
          pub: {
            allow: ["pam.>", "services.*"]
          },
          sub: {
            allow: ["_INBOX_pam.>"]
          },
        }
      },
    ]
  }
}
EOF

Reload the server to pick up the new config.

echo 'Reloading the server with new config...'
nats-server --signal reload=$SERVER_PID

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 1786fb97_default  Creating
Network 1786fb97_default  Created
             _             _               
 _ __   __ _| |_ ___      | |__   _____  __
| '_ \ / _` | __/ __|_____| '_ \ / _ \ \/ /
| | | | (_| | |_\__ \_____| |_) | (_) >  < 
|_| |_|\__,_|\__|___/     |_.__/ \___/_/\_\
                                           
nats-box v0.12.0
NATS Configuration Context "greeter"

      Server URLs: nats://127.0.0.1:4222
         Username: greeter
         Password: *********
            Token: greeter
             Path: /nsc/.config/nats/context/greeter.json

NATS Configuration Context "joe"

      Server URLs: nats://127.0.0.1:4222
         Username: joe
         Password: *********
            Token: joe
             Path: /nsc/.config/nats/context/joe.json

NATS Configuration Context "pam"

      Server URLs: nats://127.0.0.1:4222
         Username: pam
         Password: *********
            Token: pam
             Path: /nsc/.config/nats/context/pam.json

19:54:58 Listening on "services.greet" in group "NATS-RPLY-22"
19:54:59 Sending request on "services.greet"


19:54:59 [#0] Received on subject "services.greet":
19:54:59 Received with rtt 1.938223ms
Reply kVcebqrJGNaiEdYnRkxtZ6

19:54:59 Sending request on "services.greet"


19:54:59 [#1] Received on subject "services.greet":
Reply kVcebqrJGNaiEdYnRkxtbq

19:54:59 Received with rtt 1.774421ms
19:54:59 Sending request on "services.greet"
19:54:59 Subscribing on _INBOX.>
19:54:59 [#2] Received on subject "services.greet":


[#1] Received on "_INBOX.ibELuIAOztYm2U6Ft9a6U7.x32D5Jhc"
Reply kVcebqrJGNaiEdYnRkxtea

19:54:59 Received with rtt 2.942535ms
Reply kVcebqrJGNaiEdYnRkxtea

19:54:59 Sending request on "services.greet"
19:54:59 [#3] Received on subject "services.greet":


19:54:59 Unexpected NATS error from server nats://127.0.0.1:4222: nats: Permissions Violation for Subscription to "_INBOX_joe.gdH4A4t4fVkE968KULTJHP.3DNwALma"
Reloading the server with new config...
19:55:04 Unexpected NATS error from server nats://127.0.0.1:4222: nats: Permissions Violation for Subscription to "_INBOX.>" (sid "1")
19:55:04 Subscribing on _INBOX.>
nats: error: nats: Permissions Violation for Subscription to "_INBOX.>", try --help
19:55:04 Subscribing on _INBOX_joe.>
nats: error: nats: Permissions Violation for Subscription to "_INBOX_joe.>", try --help
19:55:04 Sending request on "services.greet"
19:55:04 [#4] Received on subject "services.greet":


19:55:04 Received with rtt 2.400329ms
Reply kVcebqrJGNaiEdYnRkxtk4

19:55:04 Sending request on "services.greet"
19:55:04 [#5] Received on subject "services.greet":


19:55:04 Received with rtt 1.708521ms
Reply kVcebqrJGNaiEdYnRkxtmo

Recording

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