Skip to main content

Authorization Overview

NATS Server authorization controls what authenticated users can publish and subscribe to. Permissions are granted at the subject level, providing fine-grained access control.

Permissions System

The permission system operates on two dimensions:
  1. Publish Permissions - Control which subjects a user can publish messages to
  2. Subscribe Permissions - Control which subjects a user can subscribe to
Each permission dimension supports:
  • Allow rules - Explicitly permit access to subjects
  • Deny rules - Explicitly prohibit access to subjects (override allow rules)

Basic Permission Structure

permissions {
  publish {
    allow: ["subject1", "subject2.>"]
    deny: ["subject2.admin"]
  }
  subscribe {
    allow: ["results.>", "_INBOX.>"]
    deny: ["results.internal.>"]
  }
}

Subject Permission Rules

Wildcards

NATS supports two wildcard types:
  • * - Matches a single token
  • > - Matches one or more tokens (must be last)
Examples:
permissions {
  publish {
    allow: [
      "events.*",        # events.login, events.logout
      "logs.app.>"       # logs.app.error, logs.app.info.user
    ]
  }
}

Deny Override

Deny rules always override allow rules:
permissions {
  publish {
    allow: ["data.>"]
    deny: ["data.sensitive.>"]
  }
}
This user can publish to data.public but NOT data.sensitive.passwords.

User-Level Permissions

Username/Password Users

authorization {
  users = [
    {
      user: "publisher"
      password: "$2a$11$..."
      permissions: {
        publish: ["events.>"]
        subscribe: []  # Cannot subscribe
      }
    }
    {
      user: "subscriber"
      password: "$2a$11$..."
      permissions: {
        publish: ["_INBOX.>"]
        subscribe: ["events.>"]
      }
    }
  ]
}

NKey Users

authorization {
  users = [
    {
      nkey: "UCKASD5KPQQYHB6KYD7RC62VZQN7VRU5NN2BKL7UFBQ3UBYAJQPVNHSQ"
      permissions: {
        publish: {
          allow: ["orders.>"]
          deny: ["orders.cancel"]
        }
        subscribe: {
          allow: ["results.>", "_INBOX.>"]
        }
      }
    }
  ]
}

Account-Based Authorization

Accounts provide complete isolation between groups of users.

Multi-Account Configuration

accounts {
  PROD: {
    users: [
      {
        user: "prod_user"
        password: "$2a$11$..."
        permissions: {
          publish: ["production.>"]
          subscribe: ["production.>"]
        }
      }
    ]
  }
  
  DEV: {
    users: [
      {
        user: "dev_user"
        password: "$2a$11$..."
        permissions: {
          publish: ["dev.>"]
          subscribe: ["dev.>"]
        }
      }
    ]
  }
}
Users in different accounts cannot communicate unless explicit account imports/exports are configured.

Account Isolation

Accounts provide:
  • Subject namespace isolation - production.orders in PROD is separate from production.orders in DEV
  • Resource isolation - Connection and subscription limits per account
  • Security boundaries - Complete separation between tenants

Response Permissions

Allow clients to respond to request-reply messages dynamically:
authorization {
  users = [
    {
      user: "service"
      password: "$2a$11$..."
      permissions: {
        publish: {}
        subscribe: ["requests.>"]
        responses: {
          max: 100        # Max responses allowed
          ttl: "10s"      # Response permission expires after 10s
        }
      }
    }
  ]
}

Response Permission Behavior

  1. User subscribes to requests.service
  2. Request arrives with reply subject _INBOX.abc123
  3. User granted temporary publish permission to _INBOX.abc123
  4. Permission expires after TTL or max messages reached

Default Response Limits

From source code (server/auth.go:256-261):
DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1
DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2 * time.Minute

Permission Examples

Read-Only Consumer

{
  user: "consumer"
  password: "$2a$11$..."
  permissions: {
    publish: ["_INBOX.>"]  # Only reply subjects
    subscribe: ["events.>", "data.>"]
  }
}

Publisher-Only

{
  user: "publisher"
  password: "$2a$11$..."
  permissions: {
    publish: ["events.app.>"]
    subscribe: []  # No subscriptions allowed
  }
}

Request-Reply Service

{
  user: "api_service"
  password: "$2a$11$..."
  permissions: {
    subscribe: ["api.requests.>"]
    responses: {
      max: 1
      ttl: "30s"
    }
  }
}

Admin User

{
  user: "admin"
  password: "$2a$11$..."
  permissions: {
    publish: {
      allow: [">"]
      deny: ["$SYS.>"]
    }
    subscribe: {
      allow: [">"]
      deny: ["$SYS.>"]
    }
  }
}

Service-Specific Permissions

{
  user: "order_service"
  password: "$2a$11$..."
  permissions: {
    publish: {
      allow: [
        "orders.created",
        "orders.updated",
        "orders.cancelled"
      ]
    }
    subscribe: {
      allow: [
        "orders.commands.>",
        "_INBOX.>"
      ]
    }
  }
}

Connection Type Restrictions

Restrict users to specific connection types:
authorization {
  users = [
    {
      user: "mqtt_only"
      password: "$2a$11$..."
      allowed_connection_types: ["MQTT"]
      permissions: {
        publish: ["sensors.>"]
        subscribe: ["commands.>"]
      }
    }
  ]
}
Supported connection types:
  • STANDARD - Regular NATS clients
  • WEBSOCKET - WebSocket clients
  • LEAFNODE - Leaf node connections
  • MQTT - MQTT clients

Route Permissions

Control what subjects can be imported/exported between servers in a cluster:
cluster {
  name: "production"
  
  permissions: {
    import: {
      allow: ["public.>"]
      deny: ["public.internal.>"]
    }
    export: {
      allow: ["public.>"]
      deny: ["public.internal.>"]
    }
  }
}

Account Imports and Exports

Accounts can selectively share subjects:

Export from Account

accounts {
  SERVICE: {
    exports: [
      {stream: "service.api.>"}  # Public API
      {service: "service.request.>"}  # Request-reply service
    ]
  }
}

Import into Account

accounts {
  CLIENT: {
    imports: [
      {stream: {account: "SERVICE", subject: "service.api.>"}, prefix: "external.service"}
      {service: {account: "SERVICE", subject: "service.request.>"}, to: "service.req"}
    ]
  }
}

Permission Validation

From server/auth.go implementation:

Publish Check

  1. Check deny list first - if matched, reject
  2. If allow list empty, default permit
  3. If allow list exists, subject must match

Subscribe Check

  1. Check deny list first - if matched, reject
  2. If allow list empty, default permit
  3. If allow list exists, subject must match

Testing Permissions

Test permissions before deploying:
# Test publish permission
nats-cli pub test.subject "hello" --creds=user.creds

# Test subscribe permission
nats-cli sub test.subject --creds=user.creds

Best Practices

1. Principle of Least Privilege

Grant minimal required permissions:
# Good - Specific permissions
permissions: {
  publish: ["metrics.service1.>"]
  subscribe: ["config.service1.>"]
}

# Bad - Overly permissive
permissions: {
  publish: [">"]
  subscribe: [">"]
}

2. Use Deny Rules for Exceptions

permissions: {
  publish: {
    allow: ["logs.>"]
    deny: ["logs.sensitive.>"]
  }
}

3. Organize by Account

Separate environments and tenants:
accounts {
  PROD: { ... }
  STAGING: { ... }
  DEV: { ... }
}

4. Enable Response Permissions

For request-reply patterns:
responses: {
  max: 1
  ttl: "5s"
}

Build docs developers (and LLMs) love