Skip to main content

http adapter

Delegates storage operations to a remote duck-iam API. Useful for client-server splits, microservices, and shared policy stores.

Install

import { HttpAdapter } from '@gentleduck/iam/adapters/http'
import { HttpAdapter } from '@gentleduck/iam/adapters/http'

Delegates all storage operations to a remote API via fetch. Zero dependencies.


When to use

  • Your access engine runs on a dedicated service
  • Multiple applications share a single policy store
  • Client-side code needs to evaluate permissions against a server (without exposing the database)

Usage

import { HttpAdapter } from '@gentleduck/iam/adapters/http'
import { Engine } from '@gentleduck/iam'
 
const adapter = new HttpAdapter({
  baseUrl: 'https://api.example.com/access',
  headers: {
    Authorization: 'Bearer ' + serviceToken,
  },
})
 
const engine = new Engine({ adapter })
import { HttpAdapter } from '@gentleduck/iam/adapters/http'
import { Engine } from '@gentleduck/iam'
 
const adapter = new HttpAdapter({
  baseUrl: 'https://api.example.com/access',
  headers: {
    Authorization: 'Bearer ' + serviceToken,
  },
})
 
const engine = new Engine({ adapter })

Dynamic headers

Pass a function to compute headers per-request. Useful for rotating tokens or per-request context.

const adapter = new HttpAdapter({
  baseUrl: 'https://api.example.com/access',
  headers: async () => ({
    Authorization: 'Bearer ' + (await getServiceToken()),
    'X-Request-Id': crypto.randomUUID(),
  }),
})
const adapter = new HttpAdapter({
  baseUrl: 'https://api.example.com/access',
  headers: async () => ({
    Authorization: 'Bearer ' + (await getServiceToken()),
    'X-Request-Id': crypto.randomUUID(),
  }),
})

Custom fetch

Provide your own fetch for environments without a global fetch, or to add middleware (logging, retries, tracing).

import { HttpAdapter } from '@gentleduck/iam/adapters/http'
 
const adapter = new HttpAdapter({
  baseUrl: 'https://api.example.com/access',
  fetch: async (url, init) => {
    console.log('Access API request:', url)
    const res = await globalThis.fetch(url, init)
    console.log('Access API response:', res.status)
    return res
  },
})
import { HttpAdapter } from '@gentleduck/iam/adapters/http'
 
const adapter = new HttpAdapter({
  baseUrl: 'https://api.example.com/access',
  fetch: async (url, init) => {
    console.log('Access API request:', url)
    const res = await globalThis.fetch(url, init)
    console.log('Access API response:', res.status)
    return res
  },
})

API endpoints

The HTTP adapter expects these REST endpoints on your server:

MethodPathDescription
GET/policiesList all policies
GET/policies/:idGet a single policy
PUT/policiesCreate or update a policy
DELETE/policies/:idDelete a policy
GET/rolesList all roles
GET/roles/:idGet a single role
PUT/rolesCreate or update a role
DELETE/roles/:idDelete a role
GET/subjects/:id/rolesGet a subject's roles
GET/subjects/:id/scoped-rolesGet a subject's scoped roles
POST/subjects/:id/rolesAssign a role (body: { roleId, scope? })
DELETE/subjects/:id/roles/:roleIdRevoke a role (query: ?scope=...)
GET/subjects/:id/attributesGet subject attributes
PATCH/subjects/:id/attributesUpdate subject attributes (body: attribute object)

The built-in Express adminRouter covers part of this admin surface, but it does not implement the full HTTP adapter contract. To use HttpAdapter, either implement the complete endpoint set manually or extend the admin router with the missing item lookups, scoped-role reads, and subject-attribute endpoints.


Error handling

The HTTP adapter throws an Error with the message "@gentleduck/iam HTTP {status}: {responseText}" for any non-2xx response. It does not retry failed requests. Add retry logic in your custom fetch function if needed.

A Content-Type: application/json header is sent on every request. If headers is a function, it is awaited before each request.


Constructor config

OptionTypeDefaultDescription
baseUrlstring--Base URL for the access API (trailing slash stripped)
headersRecord or () -> Record or Promise{}Static or dynamic headers
fetchtypeof fetchglobalThis.fetchCustom fetch implementation

Notes

  • HttpAdapter fetches authorization data over HTTP, but the consuming engine still evaluates permissions locally. It is not an outsourcing-evaluation adapter — it just moves storage behind a service boundary.
  • For server-side evaluation across services, expose engine.permissions() as an endpoint and consume the resulting PermissionMap on clients via the client integrations.