One Identity Safeguard JavaScript/TypeScript SDK
Check out our sample projects to get started with your own custom integration to Safeguard!
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.
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.
- TypeScript-first with full type declarations
- Dual ESM/CJS — works with
importandrequire() - 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
npm install @oneidentity/safeguardRequires Node.js 20 or later.
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();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);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();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().
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();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);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');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}`);| 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 |
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();Real-time event support requires the optional @microsoft/signalr peer dependency:
npm install @microsoft/signalrEvent 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';// 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();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).
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.
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-inTLS 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.
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.
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.
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.
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.
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
sslmodule can answer the post-handshake request (post_handshake_auth = True). No equivalent exists in Node, so safeguard.js uses TLS-version control instead.
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.
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 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);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.
- 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
CertificateRequestis disallowed under HTTP/2, and the appliance's Standard binding rejects certificate auth over HTTP/2 withHTTP_1_1_REQUIRED. undici offers HTTP/2 via ALPN by default, soNodeHttpClientautomatically pins HTTP/1.1 (allowH2: false) whenever a client certificate is present; password/token connections keep HTTP/2. Do not forceallowH2on a certificate connection. - The auto-cap only triggers when a client cert is present and no bound is
pinned. Setting
minVersionormaxVersionyourself puts you fully in control and disables the TLS 1.2 default. - Use the
TlsVersionstrings, e.g.'TLSv1.3', not Node's numeric constants.
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.
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.
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
| 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 |
See MIGRATION.md for a complete guide to upgrading from the legacy JavaScript API to the v8.0 TypeScript SDK.
| 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 |