Prerequisites

Requirement Why
Node 18+ The SDK uses the global fetch (undici) for all HTTP. On older Node you must inject a fetch implementation via config.fetch.
A running Laravel IAM server This is a thin client: it asks the server’s PDP for every decision and fetches its JWKS to verify tokens. Nothing is decided locally.
A service token An OAuth2 Client Credentials token for decisions/check, sent as Authorization: Bearer. Token verification (verifyToken) needs no token — it only reads the public JWKS.

Install

npm install @padosoft/laravel-iam-node

The only runtime dependency is jose (^5.9.6) for JWKS verification. There are no transitive HTTP libraries — transport is native fetch.

Module formats

The package ships ESM, CommonJS and TypeScript declarations, with a dedicated ./middleware subpath:

ESM / TypeScript
CommonJS
import { IamClient } from '@padosoft/laravel-iam-node';
import { requirePermission } from '@padosoft/laravel-iam-node/middleware';
const { IamClient } = require('@padosoft/laravel-iam-node');
const { requirePermission } = require('@padosoft/laravel-iam-node/middleware');

"sideEffects": false and a clean ESM build mean tree-shaking works: importing only verifyToken’s path won’t pull in the middleware.

Configuring the client

The minimum is a baseUrl. Everything else has a safe default.

import { IamClient } from '@padosoft/laravel-iam-node';

const iam = new IamClient({
  baseUrl: 'https://iam.example.com/api/iam/v1', // REQUIRED — full API base incl. route prefix
  token: process.env.IAM_SERVICE_TOKEN,          // Bearer for PDP calls
  timeoutMs: 2000,                               // per-request timeout (default 2000)
  retries: 0,                                    // idempotent network errors only (default 0)
  cache: { ttlMs: 5000 },                        // opt-in decision cache (OFF by default)
  verify: { audience: 'warehouse' },             // verifyToken defaults
});
baseUrl includes the route prefix; JWKS does not

baseUrl is the full API base, e.g. …/api/iam/v1 — PDP calls are POST {baseUrl}/decisions/check. The JWKS URL, by contrast, is derived from the origin: https://iam.example.com/.well-known/jwks.json. Keys live at the server root, not under the API prefix. Override with verify.jwksUri if your deployment differs.

A missing baseUrl throws at construction time (IamClient: \baseUrl` is required). A missing global fetchwith no injectedfetch` also throws — upgrade Node or pass one.

Pointing at a non-standard server

Two escape hatches exist for forward compatibility; you rarely need them:

Option Default Use when
checkPath decisions/check The PDP check route moved.
listResourcesPath decisions/list-resources The ReBAC list-resources route moved.

Both are trimmed of leading/trailing slashes and appended to baseUrl.

Injecting a custom fetch

For tests, proxies, mTLS agents, or Node < 18:

import { fetch as undiciFetch } from 'undici';

const iam = new IamClient({
  baseUrl: 'https://iam.example.com/api/iam/v1',
  fetch: undiciFetch, // used for BOTH decisions and JWKS fetches
});

The injected fetch is honoured everywhere, including JWKS retrieval, which is what makes the client fully testable without a network.

Verify the install

const iam = new IamClient({ baseUrl: 'https://iam.example.com/api/iam/v1' });

// With no server reachable, check() must fail closed — never throw, never allow:
const d = await iam.check({ subject: { id: 'usr_1' }, permission: 'ping' });
console.log(d.allowed); // false

If that prints false against an unreachable host, the fail-closed path is wired correctly.

Next steps