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

egain-ps-utils

v4.0.1

Published

eGain PS Utils. A utility package for eGain PS

Readme

egain-ps-utils

JWT authorizer utilities for eGain PS, built for AWS API Gateway Lambda authorizers and Azure AD (or any OpenID Connect issuer with JWKS). The package ships dual module builds (ESM + CommonJS) with full TypeScript types.

Package version: 4.0.0
Node.js: >=22.0.0 (native fetch + AbortSignal.timeout)


What changed in 4.0.0

| Area | Behavior | |------|----------| | Public API | validateToken, generateAuthResponse, getDecodedJwtToken, AuthError / AuthErrorCode, ValidateTokenOptions, PolicyDocument | | Removed | validateTokenWithSecret (AWS Secrets Manager), isTokenIssuedForValidClaimTenantId | | Config | Pass issuer + audience explicitly to validateToken — no Secrets Manager dependency | | Errors | Failures throw AuthError with stable code, optional statusCode, and standard Error.cause | | Crypto | JWKS JWK → PEM via Node crypto.createPublicKey (no third-party PEM converters) | | Network | OpenID config + JWKS fetches use a 10s timeout | | Modules | exports map: ESM importdist/esm, CJS requiredist/cjs |


Requirements

  • Node.js 22 or higher
  • Network egress from the Lambda/runtime to the issuer’s OpenID discovery and JWKS endpoints

Install

npm install egain-ps-utils
yarn add egain-ps-utils

Package entrypoints

| Condition | Path | |-----------|------| | Types | dist/cjs/index.d.ts | | import (ESM) | dist/esm/index.js | | require (CJS) | dist/cjs/index.js |

Built with:

npm run build        # clean + CJS + ESM
npm run test:cjs     # CommonJS smoke tests (after build)
npm run test:mjs     # ESM smoke tests (after build)

Features

validateToken(authorization, options)

Validates a JWT from an HTTP Authorization header against the issuer’s OpenID configuration and JWKS.

What it checks:

  1. Header is present and non-empty after stripping an optional Bearer prefix
  2. Token header contains kid
  3. issuer and audience options are non-empty strings
  4. Fetches {issuer}/.well-known/openid-configuration (trailing slash normalized)
  5. Fetches JWKS from jwks_uri, matches kid, builds RSA public key
  6. Verifies signature and claims with jsonwebtoken (algorithms: ['RS256'], issuer, audience)

Returns: { isTokenValid: true } on success
Throws: AuthError on any failure (does not return false)

type ValidateTokenOptions = {
  issuer: string;   // e.g. https://login.microsoftonline.com/<tenantId>/v2.0
  audience: string; // app ID URI or client ID expected in `aud`
};

generateAuthResponse(effect, methodArn)

Builds an API Gateway Lambda authorizer allow/deny response.

| Field | Value | |-------|--------| | principalId | 'apigateway.amazonaws.com' | | policyDocument.Version | '2012-10-17' | | policyDocument.Statement[0].Action | 'execute-api:Invoke' | | policyDocument.Statement[0].Effect | your effect (Allow / Deny) | | policyDocument.Statement[0].Resource | wildcard ARN for same API + stage (…/stage/*) when the input ARN has ≥4 /-separated parts |

Throws: AuthError (UNHANDLED, status 500) if effect or methodArn is missing.

getDecodedJwtToken(jwtString)

Decodes a JWT without verifying signature or claims. Uses jsonwebtoken.decode(jwt, { complete: true }).

Returns: { header, payload, signature } (Jwt from jsonwebtoken)
Throws: AuthError (TOKEN_DECODE_FAILED, status 401) if the token is malformed

Use only for claim inspection after (or separate from) validateToken. Decoding alone is not authentication.


Error handling (AuthError)

import { AuthError, type AuthErrorCode } from 'egain-ps-utils';

| Property | Description | |----------|-------------| | code | Stable machine-readable AuthErrorCode — branch on this | | statusCode | Optional HTTP-ish status (401, 500, …) | | message | Human-readable message | | cause | Underlying error (standard Error cause), if any | | name | Always 'AuthError' |

AuthErrorCode values

| Code | Typical meaning | Typical statusCode | |------|-----------------|----------------------| | INVALID_AUTH_HEADER | Missing/invalid Authorization header | 401 | | EMPTY_TOKEN | Bearer token empty after strip | 401 | | INVALID_ISSUER | Missing/invalid issuer option | 500 | | INVALID_AUDIENCE | Missing/invalid audience option | 500 | | TOKEN_DECODE_FAILED | JWT decode failed | 401 | | MISSING_KID | header.kid missing | 401 | | TOKEN_INVALID | Signature/claims verification failed (expired, wrong aud/iss, …) | 401 | | OPEN_ID_CONFIG_FETCH_FAILED | OpenID discovery fetch/parse failed | 500 | | SIGNING_CERT_FETCH_FAILED | JWKS fetch/parse failed | 500 | | SIGNING_CERT_KEY_NOT_FOUND | No JWKS key for token kid | 401 | | UNHANDLED | Unexpected internal error | 500 |

try {
  await validateToken(authorization, { issuer, audience });
} catch (error) {
  if (error instanceof AuthError) {
    console.error(error.code, error.statusCode, error.message, error.cause);
  }
  throw error;
}

How verification works

Authorization: Bearer <jwt>
        │
        ▼
  strip Bearer / validate header
        │
        ▼
  decode JWT → read header.kid
        │
        ▼
  GET {issuer}/.well-known/openid-configuration   (10s timeout)
        │
        ▼
  GET jwks_uri → match kid → RSA PEM (crypto.createPublicKey)
        │
        ▼
  jsonwebtoken.verify (RS256, issuer, audience)
        │
        ▼
  { isTokenValid: true }   or throw AuthError

Issuer URL: if issuer does not end with /, one is appended before resolving .well-known/openid-configuration.


Usage

API Gateway Lambda authorizer (ESM)

import {
  validateToken,
  generateAuthResponse,
  getDecodedJwtToken,
  AuthError,
} from 'egain-ps-utils';

const ISSUER = process.env.JWT_ISSUER;   // e.g. https://login.microsoftonline.com/<tenant>/v2.0
const AUDIENCE = process.env.JWT_AUDIENCE;

export const handler = async (event) => {
  const authorization =
    event.headers?.Authorization || event.headers?.authorization;

  try {
    if (!authorization) {
      throw new AuthError('INVALID_AUTH_HEADER', 'Authorization token is missing', {
        statusCode: 401,
      });
    }

    await validateToken(authorization, {
      issuer: ISSUER,
      audience: AUDIENCE,
    });

    return generateAuthResponse('Allow', event.methodArn);
  } catch (error) {
    if (error instanceof AuthError) {
      console.error('Authorization failed', {
        code: error.code,
        statusCode: error.statusCode,
        message: error.message,
      });
    } else {
      console.error('Authorization error', error);
    }
    return generateAuthResponse('Deny', event.methodArn);
  }
};

CommonJS

const {
  validateToken,
  generateAuthResponse,
  getDecodedJwtToken,
  AuthError,
} = require('egain-ps-utils');

exports.handler = async (event) => {
  const authorization =
    event.headers?.Authorization || event.headers?.authorization;

  try {
    if (!authorization) {
      throw new AuthError('INVALID_AUTH_HEADER', 'Authorization token is missing', {
        statusCode: 401,
      });
    }

    await validateToken(authorization, {
      issuer: process.env.JWT_ISSUER,
      audience: process.env.JWT_AUDIENCE,
    });

    return generateAuthResponse('Allow', event.methodArn);
  } catch (error) {
    if (error instanceof AuthError) {
      console.error('Authorization failed', {
        code: error.code,
        statusCode: error.statusCode,
        message: error.message,
      });
    } else {
      console.error('Authorization error', error);
    }
    return generateAuthResponse('Deny', event.methodArn);
  }
};

TypeScript

import {
  validateToken,
  generateAuthResponse,
  getDecodedJwtToken,
  AuthError,
  type ValidateTokenOptions,
  type PolicyDocument,
} from 'egain-ps-utils';

interface AuthorizerEvent {
  headers?: {
    Authorization?: string;
    authorization?: string;
  };
  methodArn: string;
}

const options: ValidateTokenOptions = {
  issuer: process.env.JWT_ISSUER!,
  audience: process.env.JWT_AUDIENCE!,
};

export const handler = async (event: AuthorizerEvent) => {
  const authorization =
    event.headers?.Authorization || event.headers?.authorization;

  try {
    if (!authorization) {
      throw new AuthError('INVALID_AUTH_HEADER', 'Authorization token is missing', {
        statusCode: 401,
      });
    }

    await validateToken(authorization, options);
    return generateAuthResponse('Allow', event.methodArn);
  } catch (error) {
    if (error instanceof AuthError) {
      console.error('Authorization failed', {
        code: error.code,
        statusCode: error.statusCode,
        message: error.message,
      });
    }
    return generateAuthResponse('Deny', event.methodArn);
  }
};

/** Inspect claims only — does not prove the token is valid */
export const readClaims = (jwtString: string) => {
  const decoded = getDecodedJwtToken(jwtString);
  return {
    sub: decoded.payload && typeof decoded.payload === 'object'
      ? (decoded.payload as Record<string, unknown>).sub
      : undefined,
    aud: decoded.payload && typeof decoded.payload === 'object'
      ? (decoded.payload as Record<string, unknown>).aud
      : undefined,
    exp:
      decoded.payload &&
      typeof decoded.payload === 'object' &&
      typeof (decoded.payload as { exp?: number }).exp === 'number'
        ? new Date((decoded.payload as { exp: number }).exp * 1000)
        : undefined,
  };
};

Decode then inspect (after validation)

import { validateToken, getDecodedJwtToken } from 'egain-ps-utils';

await validateToken(authorization, { issuer, audience });
const token = authorization.replace(/^Bearer\s+/i, '').trim();
const { header, payload } = getDecodedJwtToken(token);
// use payload claims (roles, oid, tid, …)

Configuration (Azure AD example)

  1. Register an app in Microsoft Entra ID (Azure AD).

  2. Note the Application (client) ID or app ID URI used as token audaudience.

  3. Issuer is typically:

    https://login.microsoftonline.com/<tenant-id>/v2.0
  4. Pass both into validateToken from env, SSM, your own secret layer, or config — this package no longer reads AWS Secrets Manager.


API reference

Exports

| Export | Kind | Description | |--------|------|-------------| | validateToken | async function | Validate Bearer JWT via issuer JWKS | | generateAuthResponse | function | Lambda authorizer policy response | | getDecodedJwtToken | function | Decode JWT (no verify) | | AuthError | class | Structured auth failure | | AuthErrorCode | type | Union of error codes | | ValidateTokenOptions | type | { issuer, audience } | | PolicyDocument | type | IAM policy shape on the authorizer response |

validateToken

validateToken(
  authorization: string,
  options: ValidateTokenOptions
): Promise<{ isTokenValid: true }>

generateAuthResponse

generateAuthResponse(
  effect: string,
  methodArn: string
): {
  principalId: string;
  policyDocument: PolicyDocument;
}

getDecodedJwtToken

getDecodedJwtToken(jwtString: string): Jwt  // jsonwebtoken Jwt (complete)

AuthError

new AuthError(
  code: AuthErrorCode,
  message: string,
  opts?: { statusCode?: number; error?: unknown }
)
// opts.error is stored as Error.cause

PolicyDocument

interface PolicyDocument {
  Version: string;
  Statement: Array<{
    Action: string;
    Effect: string;
    Resource: string;
  }>;
}

Dependency notes

| Package | Role | |---------|------| | jsonwebtoken | Decode + verify JWT (verify, decode) | | Node built-ins | fetch, AbortSignal.timeout, crypto.createPublicKey |

Runtime peers for Secrets Manager are not required.


Notes

  • Prefer instanceof AuthError and branch on code, not raw error strings.
  • validateToken always throws on failure; there is no { isTokenValid: false } success/failure tuple.
  • Expired tokens surface as TOKEN_INVALID (or network codes if discovery/JWKS cannot be reached).
  • Resource ARNs from generateAuthResponse are stage-wide wildcards when the method ARN parses as expected — fine for many authorizer setups; tighten separately if you need method-level scoping.
  • Dual package layout means the same import path works in both ESM and CommonJS consumers via the exports field.