primus-identity-validator
v1.2.0
Published
Official Node.js SDK for validating JWT tokens issued by Primus SaaS Portal. Supports Express and NestJS.
Maintainers
Readme
Primus SaaS Identity Validator - Node.js SDK
Version: 1.1.0
Library-only validator for JWT/OIDC tokens from your configured issuers (Azure AD, local, or any JWT provider). No Primus-hosted login or Primus-issued tokens.
Features
- 🔐 Multi-Issuer Support: Configure multiple identity providers (Azure AD, Local, Custom)
- ⚡ Express middleware for seamless integration
- 🎯 Role-based access control
- 🔑 Azure AD/OIDC: JWKS fetching, RS256 validation, tenant verification
- 📝 Full TypeScript support with type definitions
- ✅ 74 tests passing (100% core logic covered)
- 🚀 Intelligent JWKS caching (24-hour TTL)
Installation
npm install primus-identity-validatorQuick Start
Multi-Issuer Configuration (Recommended)
import express from 'express';
import { primusIdentityMiddleware } from 'primus-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_JWT_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 |
| jwksCacheTtl | number | 24 | JWKS cache TTL in hours |
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-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-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
- Extract
issclaim from JWT (without verifying signature) - Match issuer against configured
issuersarray - Route to appropriate validator:
type: 'oidc'→ Fetch JWKS, validate with RS256type: 'jwt'→ Validate with shared secret (HS256)
- Verify signature, issuer, audience, expiration
- Return result with claims or error
OIDC Validation Flow
For type: 'oidc' issuers:
- Fetch OpenID configuration from
{authority}/.well-known/openid-configuration - Retrieve JWKS from
jwks_uri - Find public key matching token's
kid(Key ID) - Verify RS256 signature
- Validate issuer, audience, tenant, expiration
- Cache JWKS for 24 hours
JWT Validation Flow
For type: 'jwt' issuers:
- Verify HMAC signature using shared
secret - Validate issuer matches configured
issuer - Validate audience is in
audiencesarray - 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 <token>header - Routes to correct validator based on
issclaim - Returns 401 if validation fails
- Attaches
PrimusUsertoreq.primusUseron success
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):
❌ const primusAuth = primusIdentityMiddleware({
portalUrl: '...',
clientId: '...',
clientSecret: '...',
mode: ValidationMode.AzureAd,
tenantId: '...'
});NEW (v1.1.0):
✅ 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
- Remove
ValidationModeimports (no longer exported) - Replace single config object with
issuersarray - For Azure AD: Use
type: 'oidc'withauthority - For Local: Use
type: 'jwt'withsecret - Map
clientId→audiences[0] - Map
tenantId→ extract fromissuerURL
Generating Tokens for Local JWT Issuer
[!IMPORTANT] The
secret,issuer, andaudiencesvalues used when generating tokens MUST EXACTLY MATCH your validator configuration.
Quick 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 buildTest
npm test
npm run test:coverageLint
npm run lint
npm run formatRequirements
- Node.js 16.0.0+
- Express 4.18.0+ (for middleware usage)
Documentation
- TOKEN_GENERATION_GUIDE.md - Complete guide to generating JWT tokens
- ERROR_REFERENCE.md - Troubleshooting validation errors
- PRODUCTION_DEPLOYMENT.md - Production deployment best practices
License
MIT
Support
- Documentation: https://docs.primus-saas.com
- Issues: https://github.com/akkikhan/Primus-SaaS/issues
- Email: [email protected]
