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

@primus-saas/identity-validator

v1.3.3

Published

Multi-issuer JWT/OIDC token validator for Node.js. Supports Azure AD, custom JWT issuers, and any OIDC-compliant identity provider. Works with Express and NestJS.

Readme

Primus SaaS Identity Validator - Node.js SDK

Version: 1.3.3

Library-only validator for JWT/OIDC tokens from your configured issuers (Azure AD, any OIDC authority, or JWT with secret/JWKS). No Primus-hosted login or Primus-issued tokens.

Module quick starts live in docs-site/docs/modules (Identity, Logging, Notifications). Use those instead of the removed integration guide.

Features

  • Multi-Issuer Support: Configure multiple identity providers (Azure AD, any OIDC authority, Local/JWKS JWT)
  • Express middleware for seamless integration
  • Role-based access control
  • OIDC: Discovery + JWKS fetching, RS256/RS384/RS512/ES256/ES384/ES512 validation; Azure path retains tenant verification
  • Full TypeScript support with type definitions
  • 74 tests passing (core logic covered)
  • Intelligent JWKS caching (24-hour TTL)

Installation

npm install @primus-saas/identity-validator

Quick Start

Multi-Issuer Configuration (Recommended)

import express from 'express';
import { primusIdentityMiddleware } from '@primus-saas/identity-validator';

const app = express();

// Configure multiple trusted issuers
const primusAuth = primusIdentityMiddleware({
  issuers: [
    {
      name: 'AzureAD',
      type: 'oidc',
      issuer: 'https://login.microsoftonline.com/<YOUR_TENANT_ID>/v2.0',
      authority: 'https://login.microsoftonline.com/<YOUR_TENANT_ID>/v2.0',
      audiences: ['api://your-app-id']
    },
    {
      name: 'LocalAuth',
      type: 'jwt',
      issuer: 'https://auth.yourcompany.com',
      secret: process.env.LOCAL_SECRET,
      audiences: ['api://your-app-id']
    }
  ]
});

// Protected routes
app.get('/api/protected', primusAuth, (req, res) => {
  res.json({ user: req.primusUser });
});

app.listen(3000);

Configuration

IssuerConfig Options

Each issuer in the issuers array accepts:

| Property | Type | Required | Description | |----------|------|----------|-------------| | name | string | Yes | Friendly name for this issuer | | type | 'oidc' | 'jwt' | Yes | Type of identity provider | | issuer | string | Yes | Expected iss claim value (used for routing) | | authority | string | OIDC only | Authority URL for OIDC discovery | | audiences | string[] | Yes | Valid audience values (aud claim) | | secret | string | JWT only | Shared secret for HMAC validation | | jwksUrl | string | Optional | JWKS endpoint (alternative to secret) |

Global Options

| Property | Type | Default | Description | |----------|------|---------|-------------| | clockSkew | number | 300 | Clock tolerance in seconds | | validateLifetime | boolean | true | Validate token expiration | | requireHttpsMetadata | boolean | true | Require HTTPS for OIDC metadata/JWKS (set false for local dev only) | | jwksCacheTtl | number | 24 | JWKS cache TTL in hours | | rateLimiting | object | disabled | Throttle repeated failed validations (maxFailuresPerWindow, windowSeconds, maxGlobalFailuresPerWindow) |

Usage Examples

Single Azure AD Issuer

const primusAuth = primusIdentityMiddleware({
  issuers: [
    {
      name: 'AzureAD',
      type: 'oidc',
      issuer: 'https://login.microsoftonline.com/cbd15a9b-cd52-4ccc-916a-00e2edb13043/v2.0',
      authority: 'https://login.microsoftonline.com/cbd15a9b-cd52-4ccc-916a-00e2edb13043/v2.0',
      audiences: ['e2760fbd-f134-42f4-bcda-f44306fc3fe2']
    }
  ],
  clockSkew: 300
});

Single Local JWT Issuer

const primusAuth = primusIdentityMiddleware({
  issuers: [
    {
      name: 'LocalAuth',
      type: 'jwt',
      issuer: 'http://localhost:4000',
      secret: 'your-secret-key-min-32-chars',
      audiences: ['api://my-app']
    }
  ]
});

Multi-Issuer (Hybrid)

const primusAuth = primusIdentityMiddleware({
  issuers: [
    {
      name: 'AzureAD-Production',
      type: 'oidc',
      issuer: 'https://login.microsoftonline.com/<PROD_TENANT>/v2.0',
      authority: 'https://login.microsoftonline.com/<PROD_TENANT>/v2.0',
      audiences: ['api://prod-app']
    },
    {
      name: 'AzureAD-Development',
      type: 'oidc',
      issuer: 'https://login.microsoftonline.com/<DEV_TENANT>/v2.0',
      authority: 'https://login.microsoftonline.com/<DEV_TENANT>/v2.0',
      audiences: ['api://dev-app']
    },
    {
      name: 'LocalAuth',
      type: 'jwt',
      issuer: 'http://localhost:4000',
      secret: process.env.LOCAL_SECRET,
      audiences: ['api://dev-app']
    }
  ]
});

Role-Based Access Control

import { primusIdentityMiddleware, requireRoles } from '@primus-saas/identity-validator';

// Multiple roles (user needs at least one)
app.get('/api/admin', primusAuth, requireRoles('Admin', 'SuperAdmin'), (req, res) => {
  res.json({ message: 'Admin access granted' });
});

// Single role
app.get('/api/manager', primusAuth, requireRoles('Manager'), (req, res) => {
  res.json({ message: 'Manager access' });
});

Using the Validator Directly

import { PrimusIdentityValidator } from '@primus-saas/identity-validator';

const validator = new PrimusIdentityValidator({
  issuers: [
    {
      name: 'AzureAD',
      type: 'oidc',
      issuer: 'https://login.microsoftonline.com/<TENANT>/v2.0',
      authority: 'https://login.microsoftonline.com/<TENANT>/v2.0',
      audiences: ['api://my-app']
    }
  ]
});

// Validate token manually (without "Bearer " prefix)
const token = 'eyJ0eXAiOiJKV1QiLCJhbGc...';
const result = await validator.validateToken(token);

if (result.isValid) {
  console.log('Token validated successfully');
  console.log('Claims:', result.claims);
} else {
  console.error('Validation failed:', result.error);
}

How It Works

Token Routing

  1. Extract iss claim from JWT (without verifying signature)
  2. Match issuer against configured issuers array
  3. Route to appropriate validator:
    • type: 'oidc' -> Fetch JWKS, validate with RS256
    • type: 'jwt' -> Validate with shared secret (HS256)
  4. Verify signature, issuer, audience, expiration
  5. Return result with claims or error

OIDC Validation Flow

For type: 'oidc' issuers:

  1. Fetch OpenID configuration from {authority}/.well-known/openid-configuration
  2. Retrieve JWKS from jwks_uri
  3. Find public key matching token's kid (Key ID)
  4. Verify RS256 signature
  5. Validate issuer, audience, tenant, expiration
  6. Cache JWKS for 24 hours

JWT Validation Flow

For type: 'jwt' issuers:

  1. Verify HMAC signature using shared secret
  2. Validate issuer matches configured issuer
  3. Validate audience is in audiences array
  4. Verify token expiration

API Reference

primusIdentityMiddleware(options)

Creates Express middleware that validates tokens and attaches user to req.primusUser.

Behavior:

  • Extracts token from Authorization: Bearer header
  • Routes to correct validator based on iss claim
  • Returns 401 if validation fails
  • Attaches PrimusUser to req.primusUser on success
  • Attaches tenant context to req.primusTenantContext (and req.tenantContext) when a tenantResolver is provided
  • Optional rate limiting (429 + Retry-After) for repeated failures via rateLimiting

requireRoles(...roles: string[])

Middleware that enforces role-based access control.

Returns:

  • 401 if user not authenticated
  • 403 if user lacks required role
  • Calls next() if user has at least one required role

PrimusUser Interface

interface PrimusUser {
  userId: string;              // Subject (sub claim)
  email: string;               // User email
  name: string;                // Display name
  roles: string[];             // Assigned roles
  additionalClaims: Record<string, any>;  // Other JWT claims
}

Migration from v1.0.0

Breaking Changes

The configuration structure has changed from single-mode to multi-issuer:

OLD (v1.0.0):

// Legacy configuration (Removed)
const primusAuth = primusIdentityMiddleware({
  portalUrl: '...',
  clientId: '...',
  clientSecret: '...',
  mode: ValidationMode.AzureAd,
  tenantId: '...'
});

NEW (v1.1.0):

// New configuration
const primusAuth = primusIdentityMiddleware({
  issuers: [
    {
      name: 'AzureAD',
      type: 'oidc',
      issuer: 'https://login.microsoftonline.com/<TENANT>/v2.0',
      authority: 'https://login.microsoftonline.com/<TENANT>/v2.0',
      audiences: ['<CLIENT_ID>']
    }
  ]
});

Migration Steps

  1. Remove ValidationMode imports (no longer exported)
  2. Replace single config object with issuers array
  3. For Azure AD: Use type: 'oidc' with authority
  4. For Local: Use type: 'jwt' with secret
  5. Map clientId -> audiences[0]
  6. Map tenantId -> extract from issuer URL

Generating Tokens for Local JWT Issuer

Important: The secret, issuer, and audiences values used when generating tokens MUST EXACTLY MATCH your validator configuration.

Quick Example

import { signLocalToken } from '@primus-saas/identity-validator';

const token = signLocalToken({
  userId: '123',
  email: '[email protected]',
  roles: ['Admin'],
  issuer: 'https://auth.example.com',
  audience: 'api://my-app',
  secret: process.env.LOCAL_SECRET || '',
  expiresInSeconds: 3600
});

Manual Example

const jwt = require('jsonwebtoken');

function generateLocalJwtToken(userId, email, name) {
  // Critical: Load from same environment variables
  const secret = process.env.JWT_SECRET;
  const issuer = process.env.JWT_ISSUER;
  const audience = process.env.JWT_AUDIENCE;
  
  const payload = {
    sub: userId,
    email: email,
    name: name,
    aud: audience,
    iss: issuer
  };
  
  const options = {
    expiresIn: '1h',
    issuer: issuer,
    audience: audience,
    algorithm: 'HS256'
  };
  
  return jwt.sign(payload, secret, options);
}

For complete token generation examples including frontend integration, see TOKEN_GENERATION_GUIDE.md

Troubleshooting

Common Errors

| Error | Cause | Solution | |-------|-------|----------| | invalid signature | Secret key mismatch | Ensure token generation and validation use the same secret | | Untrusted issuer | Issuer format incorrect | Use full URL format (e.g., https://localhost:4000) not name | | jwt audience invalid | Audience mismatch | Use API identifier format (e.g., api://your-app-id) | | jwt expired | Token past expiration | Generate new token or increase clockSkew |

"Untrusted issuer: {url}"

Cause: Token's iss claim doesn't match any configured issuer.

Solution: Add issuer to issuers array with exact iss value:

issuers: [
  {
    name: '...',
    type: '...',
    issuer: '<EXACT_ISS_VALUE_FROM_TOKEN>',  // Must match exactly
    // ...
  }
]

"Authority URL required for OIDC issuer"

Cause: OIDC issuer missing authority property.

Solution: Add authority URL:

{
  type: 'oidc',
  authority: 'https://login.microsoftonline.com/<TENANT>/v2.0',  // Required
  issuer: 'https://login.microsoftonline.com/<TENANT>/v2.0',
  // ...
}

"Shared secret required for JWT issuer"

Cause: JWT issuer missing secret property.

Solution: Provide shared secret:

{
  type: 'jwt',
  secret: process.env.JWT_SECRET,  // Required (min 32 chars recommended)
  issuer: 'https://...',
  // ...
}

Token Expired

Cause: Token's exp claim is in the past.

Solution: Increase clockSkew for clock drift tolerance:

{
  issuers: [...],
  clockSkew: 600  // 10 minutes tolerance
}

For detailed troubleshooting, see ERROR_REFERENCE.md

Production Deployment

Caution: Never commit secrets to source control! Use environment variables or secret management services.

Quick Checklist

  • [ ] All secrets in environment variables or vault
  • [ ] HTTPS enforced in production
  • [ ] CORS configured for production domains
  • [ ] Rate limiting enabled
  • [ ] Logging and monitoring configured

For complete deployment guide, see PRODUCTION_DEPLOYMENT.md

Development

Build

npm run build

Test

npm test
npm run test:coverage

Lint

npm run lint
npm run format

Requirements

  • Node.js 16.0.0+
  • Express 4.18.0+ (for middleware usage)

Documentation

License

MIT

Support

  • Documentation: https://primussoft.github.io/Primus-SaaS-Framework/docs/modules/identity-validator
  • Issues: https://github.com/primussoft/Primus-SaaS-Framework/issues
  • Email: [email protected]