Adventure Labs Logo
Scout Docs

Pub/sub Tokens

Verify pub/sub JWTs and sync verification keys in Rust

Pub/sub tokens

Verify short-lived JWTs for path-scoped publish (put) and subscribe (get). Claim rules and minting: Scout DB, Scout Core.

Flow

  1. A client mints a JWT, often on some application interface like a web app.
  2. The client connects to your service with the token.
  3. Your service verifies the token and then checks each path with can_publish / can_subscribe.

Your service can poll scout for new public keys. This allows for programmatic rotation across your entire fleet of devices. Example included at the bottom of this doc.

Environment

export SCOUT_PUBSUB_JWT_KEY_DIR=/path/to/public-keys   # local dir or https:// base URL

DatabaseConfig env vars are required when syncing keys from the database.

Config

PubsubTokenConfig::from_env()

  • Requires: SCOUT_PUBSUB_JWT_KEY_DIR.
  • Behavior: Loads {kid}.jwk from a local dir on startup; remote URLs fetch keys on demand by kid.
  • Returns: Result<PubsubTokenConfig>

PubsubTokenConfig::from_local_key_dir(path)

  • path: Directory of {kid}.jwk files (no DB sync).
  • Returns: Result<PubsubTokenConfig>

PubsubTokenConfig::from_public_jwks(jwks: &[&str])

  • jwks: Public JWK JSON strings, each with kid. For tests and tooling.
  • Returns: Result<PubsubTokenConfig>

sync_public_keys_from_database(&self, db: &DatabaseConfig)

  • Calls: get_pubsub_jwt_public_keys via PostgREST.
  • Behavior: Updates in-memory cache; if key dir is local, writes {kid}.jwk and removes stale files.
  • Returns: Result<()>

refresh_key_dir(&self)

  • Behavior: Reloads {kid}.jwk from a local key dir only.
  • Returns: Result<()>

Mint

mint_pubsub_token(db: &DatabaseConfig)

  • Auth: Device api_key on db (SCOUT_DEVICE_API_KEY) plus SUPABASE_PUBLIC_API_KEY.
  • Calls: Edge function mint-pubsub-token.
  • Returns: Result<PubsubTokenMint, MintPubsubTokenError> — token, put, get, iat, exp.

mint_service_pubsub_token(db: &DatabaseConfig, herd_ids: &[i64])

  • Auth: service_role_key on db (SUPABASE_SERVICE_ROLE_KEY). For trusted backends like a relay, not devices.
  • Calls: Edge function mint-pubsub-token with a { herd_ids } body; edge resolves get_service_pubsub_token_claims.
  • Returns: Result<PubsubTokenMint, MintPubsubTokenError> — publish-only (put = herd ids, get = []).

Verify

verify_pubsub_token(config, token)

  • token: JWT string from the client.
  • Returns: Result<PubsubTokenClaims, PubsubTokenError> — Invalid or Expired.
  • Claims: root, publish (put), subscribe (get), issued_at, expires_at.

can_publish(claims, path) / can_subscribe(claims, path)

  • path: Channel path (e.g. {herd_id}/live). Prefix match against root + claim patterns; empty patterns deny.

Key helpers

fetch_pubsub_jwt_public_keys(db)

  • Returns: Result<Vec<PubsubJwkPublicKey>> (kid + jwk).

sync_pubsub_public_keys_to_dir(keys, dir)

  • Behavior: Writes {kid}.jwk; deletes local *.jwk not in keys.
  • Returns: Result<()>

Example

use scout_rs::db_client::DatabaseConfig;
use scout_rs::{
    can_publish, can_subscribe, mint_pubsub_token, verify_pubsub_token, PubsubTokenConfig,
};

let db = DatabaseConfig::from_env()?;
let mint = mint_pubsub_token(&db)?;

let config = PubsubTokenConfig::from_env()?;
config.sync_public_keys_from_database(&db)?;

let claims = verify_pubsub_token(&config, &mint.token)?;
if can_subscribe(&claims, "hey/live") {
    // allow subscribe
}
if can_publish(&claims, "hey/live") {
    // allow publish
}

On this page