@auth-hub/sdk-node
v0.2.0
Published
Node.js SDK for NHS Enterprise AuthHub — Relationship-Based Access Control with OpenID AuthZEN support
Maintainers
Readme
@auth-hub/sdk-node
Node.js SDK for NHS Enterprise AuthHub — Relationship-Based Access Control as a Service.
Installation
npm install @auth-hub/sdk-nodeQuick Start
import { AuthHubClient } from '@auth-hub/sdk-node';
const client = new AuthHubClient({
endpoint: 'https://api.authhub.cloud',
apiKey: 'ahk_your_key_here',
apiSecret: 'ahs_your_secret_here',
tenantId: 'your-tenant-id',
subTenantId: 'your-sub-tenant-id',
});
// Check permission
const { allowed } = await client.check({
subject: { type: 'user', id: 'dr_mehta' },
permission: 'view_record',
resource: { type: 'patient', id: 'NHS_9876543210' },
});
if (allowed) {
// User has access — show the record
}API Reference
Permission Checks
// Single check
const result = await client.check({
subject: { type: 'user', id: 'nurse_sandra' },
permission: 'administer',
resource: { type: 'prescription', id: 'RX_001' },
});
// → { allowed: true, checkedAt: '2026-06-11T...', latencyMs: 4 }
// Batch check
const results = await client.checkBatch([
{ subject: { type: 'user', id: 'bob' }, permission: 'view', resource: { type: 'doc', id: '1' } },
{ subject: { type: 'user', id: 'bob' }, permission: 'edit', resource: { type: 'doc', id: '1' } },
]);Relationship Tuples
// Write a tuple
await client.writeTuple({
resource: { type: 'document', id: 'report-123' },
relation: 'viewer',
subject: { type: 'user', id: 'alice' },
});
// Write with group membership (indirect subject)
await client.writeTuple({
resource: { type: 'ward', id: 'cardiology' },
relation: 'nurse',
subject: { type: 'group', id: 'SG_Cardiology_Nurses', relation: 'member' },
});
// Delete a tuple
await client.deleteTuple({
resource: { type: 'patient', id: 'NHS_123' },
relation: 'assigned_ward',
subject: { type: 'ward', id: 'cardiology' },
});
// List tuples (paginated)
const { items, total } = await client.listTuples(1, 50);Schema Management
// Deploy a schema
const { zetaToken } = await client.deploySchema(`
definition user {}
definition document {
relation owner: user
relation viewer: user
permission view = viewer + owner
}
`);
// Read current schema
const schema = await client.readSchema();
// Dry-run (validate without deploying)
const diff = await client.dryRunSchema(newSchema);
console.log(diff.additions); // New types/relations
console.log(diff.removals); // Removed types/relations
console.log(diff.breaking_changes); // Breaking changesPolicy CRUD
// List policies
const policies = await client.listPolicies();
// Create a new policy
await client.createPolicy('ward', `
definition ward {
relation manager: user
relation nurse: user
permission manage = manager
permission view_patients = nurse + manager
}
`);
// Update a policy
await client.updatePolicy('ward', updatedSchema);
// Delete a policy
await client.deletePolicy('ward');Expand Relationships
// See all relationships that grant access to a resource
const tree = await client.expand({
subject: { type: 'user', id: 'dr_mehta' },
permission: 'view_record',
resource: { type: 'patient', id: 'NHS_123' },
});
// → { subject: 'user:dr_mehta', relation: 'view_record', resource: 'patient:NHS_123', children: [...] }NHS Clinical Scenarios
Patient Admission
// Assign patient to ward and treating team
await client.writeTuples([
{ resource: { type: 'patient', id: 'NHS_999' }, relation: 'assigned_ward', subject: { type: 'ward', id: 'cardiology' } },
{ resource: { type: 'patient', id: 'NHS_999' }, relation: 'treating_clinician', subject: { type: 'user', id: 'dr_mehta' } },
{ resource: { type: 'patient', id: 'NHS_999' }, relation: 'primary_nurse', subject: { type: 'user', id: 'nurse_lee' } },
]);Patient Discharge
// Remove ward assignment — ward staff lose access
await client.deleteTuple({
resource: { type: 'patient', id: 'NHS_999' },
relation: 'assigned_ward',
subject: { type: 'ward', id: 'cardiology' },
});Break-Glass Emergency Access
// Grant emergency access
await client.writeTuple({
resource: { type: 'patient', id: 'NHS_999' },
relation: 'break_glass_accessed_by',
subject: { type: 'user', id: 'dr_emergency' },
});
// ... later, revoke
await client.deleteTuple({
resource: { type: 'patient', id: 'NHS_999' },
relation: 'break_glass_accessed_by',
subject: { type: 'user', id: 'dr_emergency' },
});Middleware Integration (Express)
import express from 'express';
import { AuthHubClient } from '@auth-hub/sdk-node';
const app = express();
const authz = new AuthHubClient({ /* config */ });
// Middleware to check permission before accessing a patient record
app.get('/patients/:nhsNumber/record', async (req, res) => {
const { allowed } = await authz.check({
subject: { type: 'user', id: req.user.id },
permission: 'view_record',
resource: { type: 'patient', id: req.params.nhsNumber },
});
if (!allowed) {
return res.status(403).json({ error: 'Access denied' });
}
// Proceed with record access...
});Error Handling
import { AuthHubClient, AuthHubError } from '@auth-hub/sdk-node';
try {
await client.check({ ... });
} catch (err) {
if (err instanceof AuthHubError) {
console.error(`AuthHub error: ${err.message} (${err.statusCode})`);
}
}AuthZEN Evaluation (OpenID AuthZEN 1.0)
The SDK supports the OpenID AuthZEN standard for policy evaluation using the SARC (Subject, Action, Resource, Context) model.
Basic Evaluation
const result = await client.evaluate({
subject: { type: 'practitioner', id: 'P123' },
action: { name: 'view_record' },
resource: { type: 'patient', id: 'NHS_9876543210' },
});
if (result.decision) {
// Access granted
}Evaluation with Agent Context
const result = await client.evaluate({
subject: { type: 'practitioner', id: 'P123' },
action: { name: 'view_record' },
resource: { type: 'patient', id: 'NHS_9876543210' },
context: {
agent: {
type: 'clinical_decision_support',
id: 'cds-agent-001',
name: 'CardioAssist AI',
},
},
});AARP Break-Glass (Requestable Denial)
const result = await client.evaluate({
subject: { type: 'practitioner', id: 'P456' },
action: { name: 'view_sensitive' },
resource: { type: 'patient', id: 'NHS_1234567890' },
});
if (!result.decision && result.context?.obligations) {
// Break-glass opportunity available
const obligation = result.context.obligations[0];
console.log('Emergency access available at:', obligation.uri);
console.log('Required fields:', obligation.required_fields);
console.log('TTL:', obligation.context.ttl_seconds, 'seconds');
}COAZ Mapping (External Payload Translation)
// Send a FHIR payload with a COAZ mapping for automatic SARC translation
const result = await client.evaluate(
{
subject: { type: 'practitioner', id: '' }, // overridden by JWT
action: { name: '' }, // filled by mapping
resource: { type: '', id: '' }, // filled by mapping
},
{ coazMapping: 'epic-fhir-r4' }, // Name of active COAZ mapping
);Configuration
| Option | Required | Default | Description |
|--------|----------|---------|-------------|
| endpoint | ✓ | — | AuthHub API URL |
| apiKey | ✓ | — | API Key ID from registration |
| apiSecret | ✓ | — | API Secret from registration |
| tenantId | ✓ | — | Your tenant ID |
| subTenantId | ✓ | — | Your sub-tenant ID |
| namespacePrefix | — | auto | Namespace prefix for type isolation |
| timeoutMs | — | 10000 | Request timeout in milliseconds |
License
MIT
