Node.js SDK API Reference

Complete API reference for Hawcx OAuth Client SDK for Node.js

Node.js SDK API Reference

The SDK exposes two construction paths on HawcxOAuth:

PathConstructorWhen to use
Discovery (recommended)await HawcxOAuth.fromIssuer({ issuer, configId, clientId })New code. Discovers /.well-known/openid-configuration once at startup and enforces signature + iss + aud + exp + nbf on every verify.
Legacynew HawcxOAuth({ configId, baseUrl })4.x callers. Hits fixed endpoints <baseUrl>/oauth2/token and <baseUrl>/keys; verifies signature only — iss and aud are not checked.

Both modes share the same exchangeCode / verifyToken / clearCache methods.


HawcxOAuth (discovery mode)

HawcxOAuth.fromIssuer(config)

Async factory. Fetches <issuer>/.well-known/openid-configuration once at construction, reads the token endpoint + JWKS URI + supported id_token signing algorithm, and wires a verifier that enforces signature, iss, aud, exp, and nbf on subsequent verifyToken calls.

import { HawcxOAuth } from '@hawcx/oauth-client';

const oauth = await HawcxOAuth.fromIssuer({
  issuer: process.env.HAWCX_BASE_URL!,     // discovery + expected `iss`
  configId: process.env.HAWCX_CONFIG_ID!,  // → X-Config-Id header
  clientId: process.env.HAWCX_CLIENT_ID!,  // → expected `aud` on id_tokens
});

Config:

PropertyTypeRequiredDescription
issuerstringYesHawcx issuer URL. Discovery target and expected iss claim.
configIdstringYesTenant routing key. Sent as X-Config-Id header on token exchange.
clientIdstringYesThe value the tenant has configured (Admin Console) as the aud claim on issued id_tokens.
timeoutnumberNoPer-request timeout in ms (default 10_000).
discoveryTimeoutnumberNoDiscovery fetch timeout in ms (default 5_000).

configId and clientId are distinct

configId routes the token exchange (header); clientId is matched against the aud claim. They are configured separately in the Admin Console and may be different strings. Passing the same value for both fails verification with Unexpected JWT audience unless the tenant has configured them identically.

Throws DiscoveryError if the discovery document is unreachable, returns non-2xx, isn't valid JSON, or is missing any of token_endpoint, jwks_uri, id_token_signing_alg_values_supported. The underlying network / parse error is preserved as err.cause.

oauth.discoveryMetadata

The resolved OIDC discovery document. Useful for diagnostics — confirm which endpoints and signing algs were picked up.

oauth.discoveryMetadata?.token_endpoint;
oauth.discoveryMetadata?.jwks_uri;
oauth.discoveryMetadata?.id_token_signing_alg_values_supported;

null on clients constructed via the legacy constructor.


HawcxOAuth (legacy mode)

For 4.x callers; new code should prefer the discovery path above.

import { HawcxOAuth } from '@hawcx/oauth-client';

const oauth = new HawcxOAuth({
  configId: process.env.HAWCX_CONFIG_ID!,
  baseUrl: process.env.HAWCX_BASE_URL!,
  timeout: 10_000,
});

Config:

PropertyTypeRequiredDescription
configIdstringYesHawcx Config ID.
baseUrlstringNoDefaults to https://api.hawcx.com.
timeoutnumberNoRequest timeout in ms (default 10_000).

Verifies signature + exp + nbf. iss and aud are not checked (the legacy constructor has no way to know what to expect).


Shared methods

exchangeCode(code, codeVerifier, redirectUri?)

Exchange an authorization code for verified claims.

const { idToken, claims } = await oauth.exchangeCode(authCode, codeVerifier);

// With redirect_uri (RFC 6749 §4.1.3) when your auth request registered one:
const { idToken, claims } = await oauth.exchangeCode(
  authCode,
  codeVerifier,
  'https://app.example.com/auth/callback',
);

Returns:

PropertyTypeDescription
idTokenstringRaw JWT (do not use as access token)
claimsobjectVerified JWT claims

verifyToken(token, options?)

Verify a JWT and return its claims.

  • Discovery mode enforces signature + iss + aud + exp + nbf.
  • Legacy mode enforces signature + exp + nbf. iss and aud are not checked — the legacy SDK has no way to know what to expect.
  • options.nonce is checked in both modes when provided. If the token's nonce claim doesn't match, TokenVerificationError is thrown regardless of construction path.
const claims = await oauth.verifyToken(idToken);

// With expected nonce, when your auth request bound one:
const claims = await oauth.verifyToken(idToken, { nonce: 'expected-nonce' });

clearCache()

Clear the JWKS cache (useful for key rotation or tests).

oauth.clearCache();

Error Types

import {
  HawcxOAuthError,
  DiscoveryError,
  TokenExchangeError,
  TokenVerificationError,
} from '@hawcx/oauth-client';
  • DiscoveryErrorfromIssuer could not resolve a usable discovery document. Underlying network / parse error is preserved as err.cause.
  • TokenExchangeError — code exchange failed (invalid/expired code, network error, missing id_token in response). Carries statusCode.
  • TokenVerificationError — JWT verification failed. Messages include Token expired, Invalid signature, Token not yet valid, and (in discovery mode) Unexpected JWT audience / Unexpected JWT issuer.

Step-Up Client (Management API)

StepUpClient is the entry point for the management / step-up API (/v1/management/*) — startToken and consumeReceipt.

Recommended: reuse your OIDC signing key

withPrivateKeyJwt (Path A) is the recommended way to authenticate management API calls. It reuses the same Ed25519 private_key_jwt signing key you already registered for OIDC login — one key instead of the legacy four-key ECIES blob — and is fully standards-based (RFC 7523).

Prerequisite: enable jwt on the Management API Authentication card

Before management calls can authenticate with private_key_jwt, enable it for the project in the Admin Console → Project Settings → Management API Authentication:

  • Migrating — accepts both the legacy ECIES path and signed private_key_jwt. Use this during rollout so existing and new callers coexist.
  • Signed tokensprivate_key_jwt only (after every caller has migrated).

If the project is left on Legacy (ECIES only), private_key_jwt management calls are rejected with management_auth_mode_not_allowed (HTTP 401) — even with a correctly registered key. Note: regenerating an ECIES secret-key blob resets the project to Legacy, so set this after any blob regeneration.

Mints a short-lived per-request EdDSA Bearer JWT (iss = sub = clientId, aud = the full endpoint URL, ~60s lifetime, random jti, and a body_sha256 claim that binds the JWT to the exact request body bytes). Routing uses the X-Config-Id header.

The signing key must be Ed25519 (kty: 'OKP', crv: 'Ed25519'). It accepts a JWK object, a PKCS#8 PEM string, or a pre-imported jose KeyLike.

import { StepUpClient } from '@hawcx/oauth-client';

const privateJwk = JSON.parse(process.env.HAWCX_PRIVATE_JWK!); // OKP/Ed25519, with kid

const stepUp = StepUpClient.withPrivateKeyJwt({
  baseUrl: 'https://api.hawcx.com',
  configId: process.env.HAWCX_CONFIG_ID!,   // X-Config-Id tenant routing
  clientId: process.env.HAWCX_CLIENT_ID!,   // iss/sub in the per-request JWT
  oidcSigningKey: privateJwk,               // same key as your OIDC ClientAssertionSigner
  kid: privateJwk.kid,
});

// Begin a step-up flow
const { start_token, expires_in } = await stepUp.startToken({
  userId: '[email protected]',
  purpose: 'change_mfa_method',
  newMfaMethod: 'totp',   // 'email_otp' | 'sms_otp' | 'totp'
});

// Finalize after the user completes the MFA challenge
const { ok } = await stepUp.consumeReceipt({
  receipt: 'step-up-receipt-from-client',
});

Responses carry an X-Response-Signature JWS (ES256) that you may verify against the OIDC JWKS you already trust.

StepUpClient.fromSecretKey(options) (legacy, deprecated)

Legacy ECIES path — deprecated

StepUpClient.fromSecretKey and StepUpClient.fromKeys use the proprietary ECIES credential blob (hwx_sk_v1_…). They are deprecated but still functional and will only be removed in a future major release. Migrate to withPrivateKeyJwt at your leisure.

// DEPRECATED — migrate to withPrivateKeyJwt above
import { StepUpClient } from '@hawcx/oauth-client';

const stepUp = StepUpClient.fromSecretKey({
  baseUrl: 'https://api.hawcx.com',
  secretKey: process.env.HAWCX_SECRET_KEY!,  // hwx_sk_v1_… blob
  relyingParty: 'your-app.com',
});

Delegation Client

Use the delegation client for backend-driven MFA setup and user/device management.

DelegationClient.fromSecretKey(options)

import { DelegationClient } from '@hawcx/oauth-client';

const client = DelegationClient.fromSecretKey({
  secretKey: process.env.HAWCX_SECRET_KEY!,
  baseUrl: process.env.HAWCX_BASE_URL!,
  apiKey: process.env.HAWCX_CONFIG_ID
});

DelegationClient.fromKeys(options)

const client = DelegationClient.fromKeys({
  spSigningKey: process.env.SP_ED25519_PRIVATE_KEY_PEM!,
  spEncryptionKey: process.env.SP_X25519_PRIVATE_KEY_PEM!,
  idpVerifyKey: process.env.IDP_ED25519_PUBLIC_KEY_PEM!,
  idpEncryptionKey: process.env.IDP_X25519_PUBLIC_KEY_PEM!,
  spKid: process.env.SP_KEY_ID!,
  idpKid: process.env.IDP_KEY_ID!,
  baseUrl: process.env.HAWCX_BASE_URL!,
  apiKey: process.env.HAWCX_CONFIG_ID
});

MFA

import { MfaMethod } from '@hawcx/oauth-client';

const result = await client.mfa.initiate({
  userId: '[email protected]',
  mfaMethod: MfaMethod.SMS,
  phoneNumber: '+15551234567'
});

await client.mfa.verify({
  userId: '[email protected]',
  sessionId: result.session_id,
  otp: '123456'
});

Users

const creds = await client.users.getCredentials('[email protected]');

Devices

const devices = await client.devices.list('[email protected]');
await client.devices.revoke({ userId: '[email protected]', deviceId: 'h2index' });