Skip to content

Repository files navigation

npm license

safeguard.js

One Identity Safeguard JavaScript/TypeScript SDK


Check out our sample projects to get started with your own custom integration to Safeguard!


Support

One Identity open source projects are supported through One Identity GitHub issues and the One Identity Community. This includes all scripts, plugins, SDKs, modules, code snippets or other solutions. For assistance with any One Identity GitHub project, please raise a new Issue on the One Identity GitHub project page. You may also visit the One Identity Community to ask questions. Requests for assistance made through official One Identity Support will be referred back to GitHub and the One Identity Community forums where those requests can benefit all users.

Introduction

All functionality in Safeguard is available via the Safeguard API. There is nothing that can be done in the Safeguard UI that cannot also be performed using the Safeguard API programmatically.

safeguard.js is provided to facilitate calling the Safeguard API from JavaScript and TypeScript. It is meant to remove the complexity of dealing with authentication via Safeguard's embedded secure token service (STS). The basic usage is to create a SafeguardClient with your chosen authentication strategy, call connect(), then call API methods using the same authenticated client.

safeguard.js also provides an easy way to call Safeguard A2A from JavaScript. The A2A service requires client certificate authentication for retrieving passwords for application integration. When Safeguard A2A is properly configured, specified passwords can be retrieved with a single method call without requiring access request workflow approvals.

safeguard.js includes an SDK for listening to Safeguard's powerful, real-time event notification system. Safeguard provides role-based event notifications via SignalR to subscribed clients. The PersistentSafeguardEventListener provides automatic reconnection with token refresh for long-running listeners.

Features

  • TypeScript-first with full type declarations
  • Dual ESM/CJS — works with import and require()
  • Node.js and Browser support
  • Multiple auth strategies — Password, Certificate, PKCE (browser), PKCE Non-Interactive (headless), Device Code (Node + browser), Token, Anonymous
  • A2A client — retrieve/set passwords, SSH keys, API key secrets, broker access requests
  • SignalR events — one-shot and persistent event listeners with auto-reconnect
  • Secure by default — TLS verification enabled, no secrets in memory longer than needed

Installation

npm install @oneidentity/safeguard

Requires Node.js 20 or later.

Getting Started

Password Authentication (Node.js)

import { SafeguardClient, PasswordAuth, Service } from '@oneidentity/safeguard';

const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new PasswordAuth({
    username: 'Admin',
    password: 'Admin123',
    provider: 'Local',
  }),
});

await client.connect();

const me = await client.get(Service.CORE, 'Me');
console.log(me);

await client.disconnect();

PKCE Authentication (Browser)

import { SafeguardClient, PkceAuth, handlePkceCallback, Service } from '@oneidentity/safeguard';

// On your callback page, call this first:
handlePkceCallback();

// On your main page:
const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new PkceAuth({ redirectUri: window.location.href }),
});

await client.connect(); // Redirects to Safeguard login if no stored tokens

const me = await client.get(Service.CORE, 'Me');
console.log(me);

PKCE Non-Interactive (Headless Automation)

For automated scenarios where no browser is available (CI/CD, scripts):

import { SafeguardClient, PkceNonInteractiveAuth, Service } from '@oneidentity/safeguard';

const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new PkceNonInteractiveAuth({
    username: 'Admin',
    password: 'Admin123',
    provider: 'Local',
  }),
});

await client.connect();
const me = await client.get(Service.CORE, 'Me');
await client.disconnect();

Device Code Login (Node.js and Browser)

For headless and shared environments — containers, SSH sessions, CI/operator consoles — where the SDK cannot open a browser. The SDK requests a device code and polls; the user authenticates in their own browser on any device. Your code owns all display I/O via the required onDeviceCode callback.

This strategy is platform-agnostic and works in both Node.js and the browser.

Appliance prerequisite: the Device Code grant must be enabled in Safeguard settings (Settings -> OAuth 2.0 Grant Types; API Settings/Allowed OAuth2 Grant Types must include DeviceCode). If disabled, DeviceCodeAuth throws a ConfigurationError.

import { SafeguardClient, DeviceCodeAuth, Service } from '@oneidentity/safeguard';

const abort = new AbortController();
const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new DeviceCodeAuth({
    signal: abort.signal,
    onDeviceCode: ({ verificationUriComplete, verificationUri, userCode, expiresIn, interval }) => {
      console.log(`Open: ${verificationUriComplete ?? verificationUri}`);
      console.log(`Code: ${userCode} (expires in ${expiresIn}s; poll ${interval}s)`);
    },
  }),
});

await client.connect();
const me = await client.get(Service.CORE, 'Me');
console.log(me);

In the browser, import from @oneidentity/safeguard/browser and render the URL and code into the DOM instead of the console. Cancel with abort.abort().

Client Certificate Authentication

import { SafeguardClient, CertificateAuth, Service } from '@oneidentity/safeguard';

const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new CertificateAuth({
    certFile: '/path/to/client.pem',
    keyFile: '/path/to/client.key',
    passphrase: 'optional-key-passphrase',
  }),
});

await client.connect();
const me = await client.get(Service.CORE, 'Me');
await client.disconnect();

Anonymous Access

import { SafeguardClient, AnonymousAuth, Service } from '@oneidentity/safeguard';

const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new AnonymousAuth(),
});

await client.connect();
const status = await client.get(Service.NOTIFICATION, 'Status');
console.log(status);

Pre-existing API Token

import { SafeguardClient, TokenAuth, Service } from '@oneidentity/safeguard';

const client = new SafeguardClient('safeguard.sample.corp', {
  auth: new TokenAuth({ accessToken: 'your-token-here' }),
});

await client.connect();
const me = await client.get(Service.CORE, 'Me');

Calling the API

The client provides typed HTTP methods. Pass relative paths only — the SDK prepends the configured API version automatically (v4 by default):

// GET
const users = await client.get(Service.CORE, 'Users');

// GET with query parameters
const filtered = await client.get(Service.CORE, 'Users', {
  query: { filter: "Name eq 'Admin'", fields: 'Id,Name' },
});

// POST (create)
const newUser = await client.post(Service.CORE, 'Users', {
  json: { Name: 'newuser', PrimaryAuthenticationProvider: { Id: -1 } },
});

// PUT (update)
await client.put(Service.CORE, `Users/${newUser.Id}/Password`, {
  json: 'NewPassword123',
});

// DELETE
await client.delete(Service.CORE, `Users/${newUser.Id}`);

Services

Service Description
Service.CORE Most product functionality — access requests, asset management, policy, users
Service.APPLIANCE Appliance-specific operations — IP address, maintenance, backups
Service.NOTIFICATION Anonymous/unauthenticated — status, availability
Service.A2A Application integration — credential retrieval, access request brokering

A2A (Application to Application)

import { A2AClient, CertificateAuth, NodeHttpClient } from '@oneidentity/safeguard';

const auth = new CertificateAuth({
  certFile: 'client.pem',
  keyFile: 'client.key',
});
const a2a = new A2AClient('safeguard.sample.corp', { auth });

// A2A requires a client-cert HttpClient — it has no default and throws without one.
// On SPP 9.0 this also auto-caps the connection at TLS 1.2 so certificate auth
// works out of the box (see "TLS 1.3 and SPP 9.0").
a2a.setHttpClient(new NodeHttpClient(auth.getTlsOptions()));

// Retrieve a password
const password = await a2a.retrievePassword(apiKey);

// Retrieve an SSH private key
const sshKey = await a2a.retrievePrivateKey(apiKey);

// Set a password (write-back)
await a2a.setPassword(apiKey, 'NewPassword123');

// Discover retrievable accounts
const accounts = await a2a.getRetrievableAccounts();

Event Listeners (SignalR)

Real-time event support requires the optional @microsoft/signalr peer dependency:

npm install @microsoft/signalr

Event classes are imported from the @oneidentity/safeguard/events subpath:

import { SafeguardEventListener } from '@oneidentity/safeguard/events';
import { PasswordAuth, NodeHttpClient, MemoryStorage } from '@oneidentity/safeguard';
import * as signalR from '@microsoft/signalr';

One-Shot Listener

// Authenticate and build SignalR connection
const auth = new PasswordAuth({ username: 'Admin', password: 'Admin123', provider: 'Local' });
const httpClient = new NodeHttpClient();
const storage = new MemoryStorage();
const tokenSet = await auth.authenticate('safeguard.sample.corp', httpClient, storage);

const connection = new signalR.HubConnectionBuilder()
  .withUrl(`https://safeguard.sample.corp/service/event/signalr`, {
    accessTokenFactory: () => tokenSet.accessToken.expose(),
  })
  .withAutomaticReconnect()
  .build();

const listener = new SafeguardEventListener(connection);

listener.on('NotifyEventAsync', (event) => {
  console.log('Event received:', event);
});

await listener.start();

Persistent Listener (Auto-Reconnect)

For long-running processes that need to survive network interruptions and token expiration:

import { PersistentSafeguardEventListener } from '@oneidentity/safeguard/events';

const listener = new PersistentSafeguardEventListener(
  connection, auth, 'safeguard.sample.corp', httpClient, storage,
);

listener.onStateChange((state) => {
  console.log('State:', state);
});

listener.on('NotifyEventAsync', (event) => {
  console.log('Event:', event);
});

await listener.start();

The persistent listener automatically checks token lifetime and refreshes credentials before they expire (with a 60-second safety margin).

Security

Token Storage (Browser)

The SDK stores access tokens in memory only. Tokens do not survive a page refresh, which is the secure default for single-page applications without a backend-for-frontend (BFF).

If your application requires persistence across page reloads, you may explicitly store the token yourself — but be aware this exposes the token to cross-site scripting (XSS) attacks:

await client.connect();
// ⚠️ Customer explicitly accepts the XSS risk:
sessionStorage.setItem('my_token', client.accessToken.expose());

For high-security environments, prefer in-memory tokens with re-authentication on refresh, or implement a BFF pattern where tokens never reach the browser.

SecretValue

Credentials and access tokens are wrapped in SecretValue, a class that redacts content from toString(), toJSON(), and console.log() output. This prevents accidental logging of secrets.

To retrieve the raw string value (e.g., for HTTP headers or storage), call .expose():

const tokenSet = await auth.authenticate(host, httpClient, storage);
const raw: string = tokenSet.accessToken.expose(); // explicit opt-in

TLS Verification

TLS certificate verification is enabled by default in this SDK. The Node-side NodeHttpClient constructs an undici.Agent with rejectUnauthorized: true, so connections to an appliance whose certificate chain does not validate are refused at the TLS layer.

Production: provide a custom CA bundle

The correct way to talk to an appliance whose certificate is issued by a private / corporate CA is to provide the CA bundle to the SDK — not to disable verification:

import { readFileSync } from 'node:fs';
import { SafeguardClient, NodeHttpClient, PasswordAuth } from '@oneidentity/safeguard';

const ca = readFileSync('/etc/ssl/corp-root-ca.pem');

const client = new SafeguardClient('safeguard.corp.example', {
  auth: new PasswordAuth({ /* ... */ }),
});
client.setHttpClient(new NodeHttpClient({ ca, rejectUnauthorized: true }));
await client.connect();

NodeHttpClient accepts a PEM string or Buffer (single cert or concatenated bundle). Verification stays on; only the trust anchor changes.

Development / lab appliances (self-signed)

For local appliances with self-signed certificates the supported opt-out is the per-instance rejectUnauthorized: false flag on the HTTP client:

// Development only — never use in production
client.setHttpClient(new NodeHttpClient({ rejectUnauthorized: false }));

This affects only this SafeguardClient instance.

The NODE_TLS_REJECT_UNAUTHORIZED environment variable

Node.js honours the process-wide NODE_TLS_REJECT_UNAUTHORIZED=0 environment variable at the TLS layer below undici. If that variable is set when your program starts, all TLS verification in the entire Node process is disabled — including this SDK's connections, and any other HTTPS calls your application makes (telemetry, package mirrors, third-party APIs, etc.). Node itself prints a one-time warning when the variable is honoured.

This SDK deliberately does not override or unset this variable: it is a documented Node.js mechanism that operators sometimes set intentionally in CI or dev shells, and silently re-enabling verification from the library would be surprising. But you should be aware that:

  • It is process-wide, not SDK-specific. Setting it to bypass a lab appliance also exposes every other outbound HTTPS call in the same process.
  • It cannot be re-enabled per-connection from JavaScript once set — even new NodeHttpClient({ rejectUnauthorized: true }) is overridden by the env var.
  • It must not be set in production. Prefer the custom-CA approach above.

If you find NODE_TLS_REJECT_UNAUTHORIZED=0 in a deployment environment, treat it as a finding to remediate, not as a working configuration.

TLS 1.3 and SPP 9.0

Starting with SPP 9.0, the appliance enables TLS 1.3. For password/token authentication there is nothing to change — connections negotiate TLS 1.3 automatically. Certificate-based auth needs a little care on Node, described below. safeguard.js handles the common case for you by default.

The Node limitation: no client-side post-handshake auth

TLS 1.3 moves client-certificate authentication to a post-handshake exchange (RFC 8446 §4.6.2): the server sends a CertificateRequest after the handshake and the client must answer it. Node.js does not implement client-side post-handshake authentication — the upstream request (nodejs/node#46120) was closed NOT_PLANNED, and tls.connect exposes no toggle for it. Over a plain TLS 1.3 connection the client therefore never presents its certificate, and certificate / A2A auth fails against SPP 9.0 with 60094 Authorization is denied.

This differs from PySafeguard, where Python's ssl module can answer the post-handshake request (post_handshake_auth = True). No equivalent exists in Node, so safeguard.js uses TLS-version control instead.

What safeguard.js does by default

NodeHttpClient accepts opt-in minVersion / maxVersion TLS pins. When you build a client for certificate/A2A auth (i.e. TlsOptions carries a cert/key or pfx) and you have not pinned a version, the connection is automatically capped at TLS 1.2. That keeps the certificate request in-handshake, so certificate and A2A auth work out of the box on SPP 9.0's Standard binding:

import { SafeguardClient, CertificateAuth, NodeHttpClient } from '@oneidentity/safeguard';

const auth = new CertificateAuth({ certFile: './client.pem', keyFile: './client.key' });
const client = new SafeguardClient('safeguard.corp.example', { auth });

// cert present + no version pin ⇒ NodeHttpClient caps at TLS 1.2 for you
client.setHttpClient(new NodeHttpClient(auth.getTlsOptions()));
await client.connect();

Password/token connections carry no client certificate and continue to negotiate up to TLS 1.3.

Reaching TLS 1.3 for certificate auth (Cert SNI)

The only way to do certificate auth over TLS 1.3 from Node is the appliance's Cert SNI hostname, which requests the client certificate during the handshake (no post-handshake step). Target that hostname and pin minVersion: 'TLSv1.3'; pinning a bound explicitly disables the auto-cap:

const client = new SafeguardClient('cert-sni.safeguard.corp.example', { auth });
client.setHttpClient(
  new NodeHttpClient({ ...auth.getTlsOptions(), minVersion: 'TLSv1.3' }),
);
await client.connect();

A2A on SPP 9.0

A2A retrieval and write-back authenticate with a client certificate only — there is no password/token alternative — so on SPP 9.0's Standard binding A2A is always subject to the same TLS 1.2 auto-cap as certificate auth. A2AClient has no default HTTP client, so wire a cert-configured NodeHttpClient through setHttpClient (the auto-cap and HTTP/1.1 pin both apply through it):

import { A2AClient, CertificateAuth, NodeHttpClient } from '@oneidentity/safeguard';

const auth = new CertificateAuth({ certFile: './client.pem', keyFile: './client.key' });
const a2a = new A2AClient('safeguard.corp.example', { auth });

// cert present + no version pin ⇒ NodeHttpClient caps at TLS 1.2 for you
a2a.setHttpClient(new NodeHttpClient(auth.getTlsOptions()));
const password = await a2a.retrievePassword(apiKey);

To run A2A over TLS 1.3, use the appliance's Cert SNI hostname and pin minVersion: 'TLSv1.3' (which disables the auto-cap), exactly as for certificate auth:

const a2a = new A2AClient('cert-sni.safeguard.corp.example', { auth });
a2a.setHttpClient(
  new NodeHttpClient({ ...auth.getTlsOptions(), minVersion: 'TLSv1.3' }),
);
const password = await a2a.retrievePassword(apiKey);

Pinning the TLS version (opt-in)

NodeHttpClient accepts minVersion and maxVersion (TlsVersion = 'TLSv1.3' | 'TLSv1.2' | 'TLSv1.1' | 'TLSv1', default unset = negotiate normally):

// Require TLS 1.3 (e.g. to enforce it against SPP 9.0)
new NodeHttpClient({ minVersion: 'TLSv1.3' });

// Interim: cap the connection at TLS 1.2
new NodeHttpClient({ maxVersion: 'TLSv1.2' });

The pins govern the client's request transport (all API, token, and A2A traffic). Leaving them unset lets Node negotiate the highest mutually supported version — the recommended default for password/token auth.

Node/JS gotchas

  • No client post-handshake auth. There is no way to present a client certificate on a TLS 1.3 connection whose cert request is post-handshake. Use the TLS 1.2 default or the Cert SNI hostname — those are the only two options.
  • Keep HTTP/1.1 for certificate auth — do not enable HTTP/2. The post-handshake CertificateRequest is disallowed under HTTP/2, and the appliance's Standard binding rejects certificate auth over HTTP/2 with HTTP_1_1_REQUIRED. undici offers HTTP/2 via ALPN by default, so NodeHttpClient automatically pins HTTP/1.1 (allowH2: false) whenever a client certificate is present; password/token connections keep HTTP/2. Do not force allowH2 on a certificate connection.
  • The auto-cap only triggers when a client cert is present and no bound is pinned. Setting minVersion or maxVersion yourself puts you fully in control and disables the TLS 1.2 default.
  • Use the TlsVersion strings, e.g. 'TLSv1.3', not Node's numeric constants.

Host Validation

The SafeguardClient constructor validates the host parameter to prevent injection attacks. Only bare hostnames or IP addresses are accepted — URLs, paths, ports, query strings, and whitespace are rejected with a ConfigurationError.

Token Lifetime

client.getAccessTokenLifetimeRemaining() decodes the JWT exp claim client-side. This is a convenience for scheduling token refresh — never use it as an authorization decision. The server is the sole authority on token validity.

About the Safeguard API

The Safeguard API is a REST-based Web API. Safeguard API endpoints are called using HTTP operators and JSON requests and responses. The Safeguard API is documented using Swagger. You may use Swagger UI to call the API directly or to read the documentation about URLs, parameters, and payloads.

To access the Swagger UI use a browser to navigate to: https://<address>/service/<service>/swagger

  • <address> = Safeguard network address
  • <service> = Safeguard service to use

To access the Swagger OpenAPI specification: https://<address>/service/<service>/swagger/v4/swagger.json

Query Parameters

Parameter Example Description
filter filter=Name eq 'Admin' Filter results
fields fields=Id,Name Select specific properties
orderby orderby=Name or orderby=-Name Sort ascending/descending
page page=0 Page number (0-based)
limit limit=100 Results per page
count count=true Include total count
q q=admin Full-text search

Migration from v7.x

See MIGRATION.md for a complete guide to upgrading from the legacy JavaScript API to the v8.0 TypeScript SDK.

Related Projects

Project Language Description
SafeguardDotNet C# .NET SDK
PySafeguard Python Python SDK
SafeguardJava Java Java SDK
safeguard-ps PowerShell PowerShell module
safeguard-bash Bash Bash utilities

About

Safeguard JavaScript SDK

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

21 watching

Forks

Releases

Packages

Used by

Contributors

Languages