NATS Logo by Example

Auth Callout - Decentralized in Authentication and Authorization

Auth callout is a new feature in NATS v2.10.0 providing an extension point for integrating with alternative identity and access management (IAM) backends. See the official docs for more details.

This example demonstrates how auth callout can be configured using decentralized auth. This is combined with a basic service that handles the authorization requests delegated by the NATS server.

CLI Go Python JavaScript Rust C# Java Ruby Elixir Crystal C
Jump to the output or the recording
$ nbe run auth/callout-decentralized/cli
View the source code or learn how to run this example yourself

Code

#!/bin/bash


set -eou pipefail


NATS_URL="nats://localhost:4222"

Bootstrap the resolver

Create the operator, generate a signing key (which is a best practice), and initialize the default SYS account and sys user. Note: if this is an existing environment, this bootstrapping can be skipped.

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"

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 includes the generated resolver config.

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

Start the server.

nats-server -c server.conf > /dev/null 2>&1 &
sleep 1

Setup application accounts

Setup two application accounts for demonstration.

nsc add account APP1
nsc edit account APP1 --sk generate


nsc add account APP2
nsc edit account APP2 --sk generate

Push the two app accounts up to the server.

nsc push -A

Create a user per account.

nsc add user --account APP1 --name app1
nsc add user --account APP2 --name app2

Generate creds for the two app accounts to show that they work as expected without auth callout enabled.

nsc generate creds --account APP1 --name app1 > app1.creds
nsc generate creds --account APP2 --name app2 > app2.creds


nats --creds app1.creds pub test 'hello from app1'
nats --creds app2.creds pub test 'hello from app2'

Setup auth account for callout

Create an AUTH account which will be registered as the account for the auth callout service.

nsc add account AUTH
nsc edit account AUTH --sk generate

Create a user for the auth callout service. Extract the public key of the user so that it can be used when configuring auth callout on the account.

nsc add user --account AUTH --name auth
USER_PUB=$(nsc describe user --account AUTH --name auth --field sub | jq -r)

Generate an Xkey for auth callout.

nsc generate nkey --curve 2> auth.xk
XKEY_SEED=$(sed -n "1,1p" auth.xk)
XKEY_KEY=$(sed -n "2,1p" auth.xk)


APP1_PUB=$(nsc describe account APP1 --field sub | jq -r)
APP2_PUB=$(nsc describe account APP2 --field sub | jq -r)

Edit the AUTH account to allow it to be used by the auth callout service. The --allowed-account option is used to define which accounts this account is allowed to bind authorized users to. In this case, * indicates that any account can be bound. However if there are select accounts, they would be listed via their public nkey.

nsc edit authcallout \
  --account AUTH \
  --curve $XKEY_KEY \
  --auth-user $USER_PUB \
  --allowed-account '*'

Push the AUTH account up to the server.

nsc push -A


sleep 2

Confirm existing creds still work even with auth callout enabled.

nats --creds app1.creds pub test 'hello from app1'
nats --creds app2.creds pub test 'hello from app2'

Setup auth callout service

Next, we need the signing keys for the application accounts that the auth callout service is allowed to create and bind users to. First we extract the signing key for each account. (Helper function to copy the signing key.)

function extract_signing_key() {
  sk=$(nsc describe account $1 --field 'nats.signing_keys[0]' | tr -d '"')
  cat "/root/.local/share/nats/nsc/keys/keys/${sk:0:1}/${sk:1:2}/${sk}.nk"
}


extract_signing_key APP1 > APP1.nk
extract_signing_key APP2 > APP2.nk

We also need the signing key of the AUTH account itself to sign the responses.

extract_signing_key AUTH > AUTH.nk

In order for the auth callout service to be able to connect, we need the credentials for the auth user.

nsc generate creds --account AUTH --name auth > auth.creds

Write out a couple users emulating a user directory backend.

cat <<- EOF > users.json
{
  "alice": {
    "pass": "alice",
    "account": "APP1"
  },
  "bob": {
    "pass": "bob",
    "account": "APP2",
    "permissions": {
      "pub": {
        "allow": ["bob.>"]
      },
      "sub": {
        "allow": ["bob.>"]
      },
      "resp": {
        "max": 1
      }
    }
  }
}
EOF

Start the auth callout service passing the creds, account signing keys, as well as the Xkey seed that was generated earlier.

echo 'Starting auth callout service...'
service \
  -nats.creds=auth.creds \
  -issuer.seed=AUTH.nk \
  -xkey.seed=$XKEY_SEED \
  -signing.keys=$APP1_PUB:APP1.nk,$APP2_PUB:APP2.nk \
  -users=users.json &


sleep 2

The final requirement for clients to be able to connect is having a set of credentials of the AUTH acount which will be used to by the server to delegate to the correct auth callout service. Add a sentinel user for the AUTH account that is required to be passed along with additional credentials.

nsc add user --account AUTH --name sentinel --deny-pubsub ">"
nsc generate creds --account AUTH --name sentinel > sentinel.creds


echo 'Client request from alice...'
client \
  -creds=sentinel.creds \
  -user alice \
  -pass alice


echo 'Client request from bob...'
client \
  -creds=sentinel.creds \
  -user bob \
  -pass bob

Output

[ OK ] generated and stored operator key "OAVKZTG7ST62YCPUJYJ6LBQDMKLAUD4NRBPZ4GXUXNUJW2ATCFKKYGHV"
[ 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: OB6J6M7MHZGVNMGU5HW4DTAXYEQSDUTUJ66IT3WC45PLFSI5BRGG3SY6
[ OK ] created system_account: name:SYS id:ADCYIGJX26H2UBH3MHHYQXNFBXDC7M566RQ2TWR7S6MV6RKMFAOLREV3
[ OK ] created system account user: name:sys id:UD3BM3IUIHJ2KPWLVNIMKBRPETZODNSGKW3LREBJ5BIZMQ45WQLIMY6O
[ OK ] system account user creds file stored in `~/.local/share/nats/nsc/keys/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 "AACGHWR4ZJADWDP5R2WWSMCJCVW2U6C2EI46YE3ZUG5BS3LSGFOHUXKQ"
[ OK ] added account "APP1"
[ OK ] added signing key "AARAUQKKV4YR6Z7ZUVVAQ4556N3VUELZJYOK2AUUIAOTRKLFH6DGUSOB"
[ OK ] edited account "APP1"
[ OK ] generated and stored account key "ADW7J6ARMFQWXJLXVIKERQG6VU6VPANSPTCWS6OD25HCUGBYMO3JNOI6"
[ OK ] added account "APP2"
[ OK ] added signing key "AAFUFMWWJIT7MXQY2AJQ26Z2ER7ATUSXI4DRGAFXJNZ34DRWN4ZOQD6L"
[ OK ] edited account "APP2"
[ OK ] push to nats-server "nats://localhost:4222" using system account "SYS":
       [ OK ] push APP1 to nats-server with nats account resolver:
              [ OK ] pushed "APP1" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
       [ OK ] push APP2 to nats-server with nats account resolver:
              [ OK ] pushed "APP2" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
       [ OK ] push SYS to nats-server with nats account resolver:
              [ OK ] pushed "SYS" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
[ OK ] generated and stored user key "UCNHFM2H35DES7UXTWMR2RW7CETNPFEZRUGNH2IALANAER7NRXZZHYXP"
[ OK ] generated user creds file `~/.local/share/nats/nsc/keys/creds/local/APP1/app1.creds`
[ OK ] added user "app1" to account "APP1"
[ OK ] generated and stored user key "UCLWPUUA2BTUVVM7XBUDJQZA4G52O7XJBZODPHLOVFQ6IXI6CYM2XJCN"
[ OK ] generated user creds file `~/.local/share/nats/nsc/keys/creds/local/APP2/app2.creds`
[ OK ] added user "app2" to account "APP2"
19:30:39 Published 15 bytes to "test"
19:30:39 Published 15 bytes to "test"
[ OK ] generated and stored account key "ADGHPZKVTHHU4E6WKC2SXVI7JUEHDOQHTRG3VEKPVSONYCCZ5SWIEQ47"
[ OK ] added account "AUTH"
[ OK ] added signing key "ADKBJ7UDVMVTOPED7IXNYMTD6IASSD5UKSGDHYDBCSYBWIFRXG6OUBJ6"
[ OK ] edited account "AUTH"
[ OK ] generated and stored user key "UDZANJLUPZQBIHY6OBC7BDPDQLGGPBA2NFWCD5D4UHOLPRPWDV52CXHN"
[ OK ] generated user creds file `~/.local/share/nats/nsc/keys/creds/local/AUTH/auth.creds`
[ OK ] added user "auth" to account "AUTH"
[ OK ] added user "UDZANJLUPZQBIHY6OBC7BDPDQLGGPBA2NFWCD5D4UHOLPRPWDV52CXHN"
[ OK ] added account "*"
[ OK ] set curve key XC6EEPFGEHLZZ2Q45G3XYUD7FBU5WTYQLU4BOUO2AYM2MDMUAD3RXL2C
[ OK ] push to nats-server "nats://localhost:4222" using system account "SYS":
       [ OK ] push APP1 to nats-server with nats account resolver:
              [ OK ] pushed "APP1" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
       [ OK ] push APP2 to nats-server with nats account resolver:
              [ OK ] pushed "APP2" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
       [ OK ] push AUTH to nats-server with nats account resolver:
              [ OK ] pushed "AUTH" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
       [ OK ] push SYS to nats-server with nats account resolver:
              [ OK ] pushed "SYS" to nats-server NCQCZSICC22MKDYHNEDDISR6Q6YHZ23SQSSLO2RAUJ2XCTRKPKSXKTPX: jwt updated
              [ OK ] pushed to a total of 1 nats-server
19:30:46 Published 15 bytes to "test"
19:30:46 Published 15 bytes to "test"
Starting auth callout service...
[ OK ] added deny pub ">"
[ OK ] added deny sub ">"
[ OK ] generated and stored user key "UCHW2DVE6TZ3XYBU435BEGLVLNGVMZGSXEARFXS7GAZTKDN6MO53OC7E"
[ OK ] generated user creds file `~/.local/share/nats/nsc/keys/creds/local/AUTH/sentinel.creds`
[ OK ] added user "sentinel" to account "AUTH"
Client request from alice...
Client request from bob...
2023/10/23 19:30:48 responding to authorization request
2023/10/23 19:30:48 alice connected to nats://127.0.0.1:4222
2023/10/23 19:30:48 responding to authorization request
2023/10/23 19:30:48 bob connected to nats://127.0.0.1:4222

Recording

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