npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hawcx/oauth-client

v4.0.1

Published

Simple OAuth client for Hawcx authentication with delegation support

Readme

@hawcx/oauth-client

Simple, production-ready OAuth client SDK for Hawcx authentication.

Features

  • Simple JWT Verification - Uses jose library for automatic JWKS caching and signature handling
  • PKCE Support - Native PKCE (RFC 7636) support for enhanced OAuth security
  • Delegation API - Server-to-server user management (MFA, devices)
  • Step-Up Auth - Step-up authentication flows for sensitive operations
  • TypeScript First - Full type definitions included
  • Zero Config Crypto - All cryptographic complexity hidden behind a single secret key

Installation

npm install @hawcx/oauth-client

Quick Start

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

const oauth = new HawcxOAuth({ configId: 'your-config-id' });

// Exchange authorization code for tokens
const { idToken, claims } = await oauth.exchangeCode(code, codeVerifier);
console.log(claims.sub);   // user ID
console.log(claims.email); // user email

Modules

| Module | Purpose | |--------|---------| | OAuth | Token exchange and JWT verification | | Delegation | Server-to-server user management (MFA, devices) | | Step-Up | Step-up authentication flows |


OAuth Module

Core module for OAuth 2.1 + PKCE authentication.

HawcxOAuth

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

const oauth = new HawcxOAuth({
  configId: 'your-config-id',        // Required: Your Hawcx config ID
  baseUrl: 'https://api.hawcx.com',  // Optional: API base URL
  timeout: 10000,                     // Optional: Request timeout in ms
});

Methods

exchangeCode(code, codeVerifier): Promise<ExchangeResult>

Exchange an authorization code for tokens.

const { idToken, claims } = await oauth.exchangeCode(
  'authorization-code',
  'pkce-code-verifier'
);

Returns:

interface ExchangeResult {
  idToken: string;    // Raw JWT token
  claims: JwtClaims;  // Verified claims
}

verifyToken(token): Promise<JwtClaims>

Verify an existing JWT token.

const claims = await oauth.verifyToken(idToken);

Returns:

interface JwtClaims {
  sub?: string;            // User ID
  iss?: string;            // Issuer
  aud?: string | string[]; // Audience
  iat?: number;            // Issued at (unix timestamp)
  exp?: number;            // Expiration (unix timestamp)
  email?: string;          // User email
  name?: string;           // User name
  amr?: string[];          // Authentication methods
  mfa_method?: string;     // MFA method used
}

clearCache(): void

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

oauth.clearCache();

Errors

import {
  HawcxOAuthError,        // Base error class
  TokenExchangeError,     // Code exchange failed
  TokenVerificationError  // JWT verification failed
} from '@hawcx/oauth-client';

try {
  await oauth.exchangeCode(code, verifier);
} catch (error) {
  if (error instanceof TokenExchangeError) {
    console.log(error.statusCode); // HTTP status code
  }
  if (error instanceof TokenVerificationError) {
    console.log(error.message); // "Token expired", "Invalid signature", etc.
  }
}

Delegation Module

Server-to-server API for managing users, MFA, and devices. Requires a Hawcx secret key.

DelegationClient

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

const client = DelegationClient.fromSecretKey({
  baseUrl: 'https://api.hawcx.com',
  secretKey: process.env.HAWCX_SECRET_KEY!,
  apiKey: 'optional-api-key',  // Optional
  timeoutSeconds: 15,          // Optional (default: 15)
  clockSkewSeconds: 300,       // Optional (default: 300)
});

MFA Operations

client.mfa.initiate(options): Promise<object>

Initiate MFA setup or change for a user.

// Email MFA
const result = await client.mfa.initiate({
  userId: '[email protected]',
  mfaMethod: MfaMethod.EMAIL,
});

// SMS MFA (requires phone number)
const result = await client.mfa.initiate({
  userId: '[email protected]',
  mfaMethod: MfaMethod.SMS,
  phoneNumber: '+1234567890',
});

// TOTP MFA
const result = await client.mfa.initiate({
  userId: '[email protected]',
  mfaMethod: MfaMethod.TOTP,
});

// Change MFA method
const result = await client.mfa.initiate({
  userId: '[email protected]',
  mfaMethod: MfaMethod.EMAIL,   // Current method for verification
  mfaChangeTo: MfaMethod.TOTP,  // New method to switch to
});

MFA Methods:

enum MfaMethod {
  EMAIL = 'email',
  SMS = 'sms',
  TOTP = 'totp',
}

client.mfa.verify(options): Promise<object>

Verify MFA setup with OTP.

const result = await client.mfa.verify({
  userId: '[email protected]',
  sessionId: 'session-from-initiate',
  otp: '123456',  // 6-digit code
});

User Operations

client.users.getCredentials(userId): Promise<object>

Get user credentials/metadata.

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

Device Operations

client.devices.list(userId): Promise<object>

List all devices for a user.

const { devices } = await client.devices.list('[email protected]');

client.devices.revoke(options): Promise<object>

Revoke a device (block it from authenticating).

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

client.devices.unrevoke(options): Promise<object>

Unrevoke a previously revoked device.

await client.devices.unrevoke({
  userId: '[email protected]',
  deviceId: 'device-h2index',
});

client.devices.delete(options): Promise<object>

Permanently delete a device.

await client.devices.delete({
  userId: '[email protected]',
  deviceId: 'device-h2index',
});

Generic Request

client.request<TReq, TRes>(options): Promise<TRes>

Send a custom encrypted request to any delegation endpoint.

const result = await client.request({
  endpoint: '/hc_auth/v5/custom/endpoint',
  payload: { userid: '[email protected]', custom_field: 'value' },
  headers: { 'X-Custom-Header': 'value' },
  includeBaseUrl: true,  // Optional (default: true)
});

Credential Utilities

parseHawcxSecretKey(secretKey): ParsedCredentials

Parse a secret key into usable key material (advanced usage).

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

const creds = parseHawcxSecretKey(process.env.HAWCX_SECRET_KEY!);
// Returns: { kid, hkid, signingKeyPem, verifyKeyPem, encryptKeyPem, decryptKeyPem }

generateCredentialBlob(options): string

Generate a new credential blob (for admin tooling).

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

const secretKey = generateCredentialBlob({
  kid: 'customer-key-id',
  hkid: 'hawcx-key-id',
  edPrivate: Buffer.alloc(32),     // Ed25519 private key (32 bytes)
  xPrivate: Buffer.alloc(32),      // X25519 private key (32 bytes)
  hawcxEdPublic: Buffer.alloc(32), // Hawcx Ed25519 public key (32 bytes)
  hawcxXPublic: Buffer.alloc(32),  // Hawcx X25519 public key (32 bytes)
});
// Returns: "hwx_sk_v1_<base64url>"

Delegation Errors

import {
  DelegationError,         // Base delegation error
  DelegationCryptoError,   // Encryption/signing failed
  DelegationRequestError,  // Request to IDP failed
  DelegationResponseError, // Invalid response from IDP
} from '@hawcx/oauth-client';

try {
  await client.mfa.initiate({ userId: '[email protected]', mfaMethod: MfaMethod.EMAIL });
} catch (error) {
  if (error instanceof DelegationRequestError) {
    console.log(error.statusCode);   // HTTP status
    console.log(error.responseBody); // Response body
  }
}

Step-Up Module

For step-up authentication flows (e.g., changing MFA method requires re-authentication).

StepUpClient

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

const stepUp = StepUpClient.fromSecretKey({
  baseUrl: 'https://api.hawcx.com',
  secretKey: process.env.HAWCX_SECRET_KEY!,
  relyingParty: 'your-app.com',       // Required
  apiKey: 'optional-api-key',          // Optional
  apiPrefix: '/v1',                    // Optional (default: '/v1')
  timeoutSeconds: 15,                  // Optional
  clockSkewSeconds: 300,               // Optional
});

Methods

stepUp.startToken(options): Promise<StepUpStartTokenResponse>

Start a step-up authentication flow.

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

stepUp.consumeReceipt(options): Promise<StepUpConsumeResponse>

Consume a step-up receipt after user completes authentication.

const { ok } = await stepUp.consumeReceipt({
  receipt: 'step-up-receipt-from-client',
});

if (ok) {
  // Step-up verified, proceed with sensitive operation
}

Types

type StepUpPurpose = 'change_mfa_method';

type HxAuthMfaMethod = 'sms_otp' | 'email_otp' | 'totp';

interface StepUpStartTokenResponse {
  start_token: string;
  expires_in: number;
}

interface StepUpConsumeResponse {
  ok: boolean;
}

Examples

Next.js Auth Callback

// app/api/auth/callback/route.ts
import { HawcxOAuth, TokenExchangeError } from '@hawcx/oauth-client';
import { cookies } from 'next/headers';

const oauth = new HawcxOAuth({
  configId: process.env.HAWCX_CONFIG_ID!,
});

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');

  const cookieStore = cookies();
  const codeVerifier = cookieStore.get('code_verifier')?.value;

  if (!code || !codeVerifier) {
    return Response.redirect('/login?error=missing_params');
  }

  try {
    const { idToken, claims } = await oauth.exchangeCode(code, codeVerifier);
    // Create session with claims.sub, claims.email, etc.
    return Response.redirect('/dashboard');
  } catch (error) {
    if (error instanceof TokenExchangeError) {
      console.error('Token exchange failed:', error.message);
    }
    return Response.redirect('/login?error=auth_failed');
  }
}

Admin MFA Management

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

const admin = DelegationClient.fromSecretKey({
  baseUrl: process.env.HAWCX_API_URL!,
  secretKey: process.env.HAWCX_SECRET_KEY!,
});

// Reset user MFA to email
async function resetUserMfa(userId: string, otp: string) {
  const initResult = await admin.mfa.initiate({
    userId,
    mfaMethod: MfaMethod.EMAIL,
  });

  const verifyResult = await admin.mfa.verify({
    userId,
    sessionId: initResult.session_id,
    otp,
  });

  return verifyResult;
}

// Revoke all user devices
async function revokeAllDevices(userId: string) {
  const { devices } = await admin.devices.list(userId);

  for (const device of devices) {
    await admin.devices.revoke({
      userId,
      deviceId: device.h2index,
    });
  }
}

Step-Up for MFA Change

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

const stepUp = StepUpClient.fromSecretKey({
  baseUrl: process.env.HAWCX_API_URL!,
  secretKey: process.env.HAWCX_SECRET_KEY!,
  relyingParty: 'myapp.com',
});

const delegation = DelegationClient.fromSecretKey({
  baseUrl: process.env.HAWCX_API_URL!,
  secretKey: process.env.HAWCX_SECRET_KEY!,
});

async function changeMfaMethod(userId: string, newMethod: 'totp' | 'sms_otp' | 'email_otp') {
  // 1. Start step-up flow
  const { start_token } = await stepUp.startToken({
    userId,
    purpose: 'change_mfa_method',
    newMfaMethod: newMethod,
  });

  // 2. Send start_token to client, user completes step-up auth
  // 3. Client sends back receipt

  // 4. Verify step-up receipt
  const { ok } = await stepUp.consumeReceipt({ receipt: 'receipt-from-client' });

  if (!ok) {
    throw new Error('Step-up verification failed');
  }

  // 5. Now safe to change MFA
  await delegation.mfa.initiate({
    userId,
    mfaMethod: MfaMethod.EMAIL,
    mfaChangeTo: newMethod,
  });
}

Environment Variables

| Variable | Description | |----------|-------------| | HAWCX_CONFIG_ID | Your Hawcx OAuth config ID (for token verification) | | HAWCX_SECRET_KEY | Your Hawcx secret key (for delegation/step-up APIs) | | HAWCX_API_URL | API base URL (default: https://api.hawcx.com) |

Secret Key Format

The HAWCX_SECRET_KEY is a compact credential blob:

hwx_sk_v1_<base64url-encoded-json>

It contains:

  • Your Ed25519 signing key (for request authentication)
  • Your X25519 decryption key (for response decryption)
  • Hawcx's Ed25519 public key (for response verification)
  • Hawcx's X25519 public key (for request encryption)
  • Key IDs for rotation tracking

Generate via the Hawcx Dashboard or admin tools.


JWKS Caching

The SDK automatically caches JWKS (JSON Web Key Set) for JWT verification:

  • Keys are fetched lazily on first verifyToken() call
  • Cached in memory for the lifetime of the HawcxOAuth instance
  • Auto-refreshes if a key ID isn't found (handles key rotation)
  • Call clearCache() to force a refresh
// Keys fetched once, cached thereafter
const claims1 = await oauth.verifyToken(token1); // Fetches JWKS
const claims2 = await oauth.verifyToken(token2); // Uses cache
const claims3 = await oauth.verifyToken(token3); // Uses cache

// Force refresh if needed
oauth.clearCache();
const claims4 = await oauth.verifyToken(token4); // Fetches JWKS again

Requirements

  • Node.js >= 18.0.0

License

MIT