What is a JWT (and how it’s generated)?

A JWT (JSON Web Token) is just a signed JSON blob. It has 3 parts:

  1. Header – algorithm + key id (e.g., { "alg": "RS256", "kid": "key-2025-10" })
  2. Payload (claims) – who the user is, which org they belong to, expiry, etc.
  3. Signature – cryptographic signature over header+payload to prevent tampering.

Minimal payload you’ll want

  • sub: user id
  • orgId: the tenant/org id
  • roles: authorization roles
  • iat, exp: issued-at and expiry
  • iss, aud: issuer and audience
  • jti: unique token id (for revocation/uniqueness)

Two common signing models

  • HS256 (shared secret): same symmetric secret to sign/verify.
  • RS256/ES256 (public/private keys): private key signs, public key verifies. Easier for rotation & zero-trust between services.

How orgId fits in (and “unique” JWTs)

  • orgId goes into the token payload as a claim. This does not by itself make the token unique; jti does.
  • Token uniqueness: set a fresh, random jti (UUID) each time you issue an access token.
  • Tenant isolation:

    • Put orgId in the claims.
    • The API reads orgId from the JWT, never from the request body.
    • Optionally: use per-org signing keys or per-org secrets so a token issued for Org A can’t be verified for Org B.

Practical code (Node/TypeScript)

import jwt from "jsonwebtoken";
import { randomUUID } from "crypto";

const PRIVATE_KEY = process.env.AUTH_PRIVATE_KEY!; // PEM
const PUBLIC_KEY = process.env.AUTH_PUBLIC_KEY!; // PEM

type Claims = {
  sub: string; // userId
  orgId: string; // tenant
  roles: string[]; // ["admin","trader"]
  jti: string; // unique token id
  iss: string; // issuer
  aud: string; // audience
};

export function issueAccessToken({
  userId,
  orgId,
  roles,
  ttlSeconds = 900, // 15 min
  kid = "key-2025-10",
}: {
  userId: string;
  orgId: string;
  roles: string[];
  ttlSeconds?: number;
  kid?: string;
}) {
  const now = Math.floor(Date.now() / 1000);
  const claims: Claims = {
    sub: userId,
    orgId,
    roles,
    jti: randomUUID(),
    iss: "https://auth.quub.exchange",
    aud: "quub.exchange.api",
  };

  return jwt.sign(claims, PRIVATE_KEY, {
    algorithm: "RS256",
    keyid: kid,
    expiresIn: ttlSeconds,
    notBefore: 0,
    // iat is auto-filled unless you set it; you can also pass { iat: now }
  });
}

export function verifyAccessToken(token: string) {
  // You’ll normally resolve PUBLIC_KEY by `kid` via JWKS.
  return jwt.verify(token, PUBLIC_KEY, {
    algorithms: ["RS256"],
    audience: "quub.exchange.api",
    issuer: "https://auth.quub.exchange",
  }) as jwt.JwtPayload; // contains orgId, sub, roles, jti, iat, exp...
}

HS256 (simple, single secret)

import jwt from "jsonwebtoken";
import { randomUUID } from "crypto";

const SECRET = process.env.AUTH_SECRET!;

export function issueAccessTokenHS({
  userId,
  orgId,
  roles,
}: {
  userId: string;
  orgId: string;
  roles: string[];
}) {
  return jwt.sign(
    {
      sub: userId,
      orgId,
      roles,
      jti: randomUUID(),
      iss: "https://auth.quub.exchange",
      aud: "quub.exchange.api",
    },
    SECRET,
    { algorithm: "HS256", expiresIn: "15m" }
  );
}

Making JWTs “per-org unique” (multi-tenant options)

You have three good patterns—pick one:

  1. Single issuer & key + orgId claim

    • One keypair (or secret) for all orgs.
    • orgId is always in claims and enforced at the API.
    • Uniqueness via jti.
    • Pros: simplest ops. Cons: revocation & key rotation are global.
  2. Per-org keys (RS256)

    • Each org has its own keypair (kid ties token to org key).
    • API uses JWKS to pick the right public key based on kid.
    • If a key leaks, only that org is affected.
    • Pros: strong isolation; cleaner revocation per org. Cons: more ops complexity.
  3. Per-org shared secret (HS256)

    • Each org has its own secret (via secret manager).
    • API selects verifier secret by org context (from token’s iss/kid or routing).
    • Pros: simple. Cons: secret distribution risk, rotate carefully.

In all options, jti makes every token instance unique, and orgId is the tenant scope that your API trusts only from the token, never from request bodies.


Validation & enforcement checklist (what the API must do)

  • Verify signature (RS256/ES256 via JWKS; HS256 via secret).
  • Check exp/nbf (expiry/not-before).
  • Check iss & aud (right issuer & audience).
  • Read orgId from token (claim), never from request payload.
  • (Optional) Match URL org: :orgId in path must equal JWT orgId.
  • Enforce RLS in DB using the JWT orgId.
  • Use jti to support revocation lists/allow-lists when needed.

Quick answers to your implied questions

  • How is JWT generated? By signing JSON claims (with orgId, sub, etc.) using either a private key (RS256/ES256) or a shared secret (HS256).

  • Can orgId make the token unique? Not by itself. Use jti for uniqueness; use orgId for tenant scoping.

  • How to ensure tokens can’t be reused across orgs? Include orgId in claims and enforce it in the API; optionally use per-org keys/secrets so a token for Org A can’t even verify under Org B.