@zewstid/node
Official ZewstID SDK for Node.js - Authentication for backend applications.
✨ Features
Authentication & Authorization (v0.1.0)
- ✨ JWT Validation - Fast and secure token verification
- 🔐 JWKS Caching - Automatic key rotation with intelligent caching
- 🛡️ Role-Based Access Control - Built-in RBAC middleware
- 🔑 Token Introspection - Validate and introspect access tokens
- ⚡ High Performance - Optimized for production workloads
- 🎯 TypeScript - Full type safety out of the box
Framework Integration
- 📦 Express Middleware - Ready-to-use authentication middleware
- ⚡ Fastify Plugin - Native Fastify support
- 🔧 Standalone Client - Use in any Node.js environment
- 🎨 Flexible - Works with Koa, Hapi, or vanilla Node.js
Developer Experience
- ⚡ Minimal Dependencies - Uses only for JWT handling
jose - 🐛 Debug Mode - Detailed logging for troubleshooting
- 📖 Comprehensive Docs - Clear examples and API reference
- 🔄 Auto Key Rotation - JWKS keys cached with auto-refresh
🚀 Installation
From ZewstID npm Registry:
npm install @zewstid/node --registry https://npm.zewstid.com/
Or configure registry in .npmrc
# .npmrc @zewstid:registry=https://npm.zewstid.com/
Then install normally:
npm install @zewstid/node
Quick Start
Standalone Usage
import { ZewstID } from '@zewstid/node'; const zewstid = new ZewstID({ domain: 'auth.zewstid.com', clientId: 'your_client_id', }); // Validate a JWT token const user = await zewstid.getUser(token); console.log(user);
Express Middleware
import express from 'express'; import { requireAuth } from '@zewstid/node/express'; const app = express(); // Protected route app.get('/api/protected', requireAuth({ domain: 'auth.zewstid.com', clientId: process.env.ZEWSTID_CLIENT_ID, }), (req, res) => { res.json({ message: `Hello ${req.user?.name}`, user: req.user, }); } ); app.listen(3000);
API Reference
ZewstID Class
ZewstIDMain client for token validation and user management.
Constructor:
const zewstid = new ZewstID({ domain?: string; // Default: "auth.zewstid.com" clientId?: string; // Your client ID clientSecret?: string; // For confidential operations audience?: string; // Expected JWT audience jwksCacheDuration?: number; // JWKS cache in seconds (default: 3600) debug?: boolean; // Enable debug logging });
Methods:
validateToken(token, options?)
validateToken(token, options?)Validate a JWT access token.
const payload = await zewstid.validateToken(token, { audience: 'my-api', maxAge: 3600, // Maximum token age in seconds });
getUser(token)
getUser(token)Validate token and get user information.
const user = await zewstid.getUser(token); // Returns: { id, name, email, roles, ... }
getUserInfo(accessToken)
getUserInfo(accessToken)Get user info from the
/userinfoconst user = await zewstid.getUserInfo(accessToken);
introspectToken(token)
introspectToken(token)Introspect a token (requires client credentials).
const result = await zewstid.introspectToken(token); // Returns: { active: true, scope: '...', ... }
revokeToken(token)
revokeToken(token)Revoke a token (requires client credentials).
await zewstid.revokeToken(token);
getOpenIDConfiguration()
getOpenIDConfiguration()Get OpenID configuration.
const config = await zewstid.getOpenIDConfiguration();
Express Middleware
requireAuth(options)
requireAuth(options)Require authentication for a route.
import { requireAuth } from '@zewstid/node/express'; app.get('/protected', requireAuth({ domain: 'auth.zewstid.com', clientId: 'your_client_id', debug: true, }), (req, res) => { // req.user is available // req.token contains the access token res.json({ user: req.user }); } );
Options:
interface ZewstIDMiddlewareOptions { // All ZewstID config options plus: getToken?: (req: Request) => string | null; // Custom token extractor onError?: (error: Error, req: Request, res: Response) => void; // Custom error handler }
optionalAuth(options)
optionalAuth(options)Optional authentication (doesn't block if no token).
app.get('/public', optionalAuth({ clientId: 'your_client_id' }), (req, res) => { if (req.user) { res.json({ message: `Hello ${req.user.name}` }); } else { res.json({ message: 'Hello anonymous' }); } } );
requireRole(role, options?)
requireRole(role, options?)Require specific role(s).
import { requireAuth, requireRole } from '@zewstid/node/express'; app.get('/admin', requireAuth({ clientId: 'your_client_id' }), requireRole('admin'), // Single role (req, res) => { res.json({ message: 'Admin access' }); } ); // Multiple roles (user needs at least one) app.get('/moderator', requireAuth({ clientId: 'your_client_id' }), requireRole(['admin', 'moderator']), (req, res) => { res.json({ message: 'Moderator access' }); } );
requireAllRoles(roles, options?)
requireAllRoles(roles, options?)Require all specified roles.
import { requireAuth, requireAllRoles } from '@zewstid/node/express'; app.get('/super-admin', requireAuth({ clientId: 'your_client_id' }), requireAllRoles(['admin', 'verified']), (req, res) => { res.json({ message: 'Super admin access' }); } );
Advanced Usage
Custom Token Extraction
By default, tokens are extracted from the
Authorization: Bearer <token>requireAuth({ clientId: 'your_client_id', getToken: (req) => { // Extract from cookie return req.cookies.access_token; // Or from query parameter // return req.query.token as string; // Or from custom header // return req.headers['x-api-key'] as string; }, })
Custom Error Handling
requireAuth({ clientId: 'your_client_id', onError: (error, req, res) => { console.error('Auth error:', error); res.status(401).json({ error: 'Custom error message', details: error.message, }); }, })
Validate Token Without Middleware
import { ZewstID } from '@zewstid/node'; const zewstid = new ZewstID({ clientId: 'your_client_id', }); async function checkAuth(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'No token' }); } try { const user = await zewstid.getUser(token); req.user = user; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); } }
Role-Based Authorization
function checkRole(role: string) { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Not authenticated' }); } if (!req.user.roles?.includes(role)) { return res.status(403).json({ error: 'Forbidden', message: `Required role: ${role}`, }); } next(); }; } app.get('/admin', requireAuth({ clientId: 'your_client_id' }), checkRole('admin'), (req, res) => { res.json({ message: 'Admin only' }); } );
Token Introspection
const zewstid = new ZewstID({ clientId: 'your_client_id', clientSecret: 'your_client_secret', }); const result = await zewstid.introspectToken(token); if (result.active) { console.log('Token is valid'); console.log('Scope:', result.scope); console.log('Expires:', new Date(result.exp! * 1000)); } else { console.log('Token is invalid or expired'); }
TypeScript
Full TypeScript support included.
import type { ZewstIDConfig, ZewstIDUser, ZewstIDTokenPayload, ValidateTokenOptions, } from '@zewstid/node'; // Express Request is automatically extended import { Request } from 'express'; function handler(req: Request) { // req.user is typed as ZewstIDUser | undefined // req.token is typed as string | undefined const user: ZewstIDUser | undefined = req.user; }
Environment Variables
ZEWSTID_CLIENT_ID=your_client_id_here ZEWSTID_CLIENT_SECRET=your_client_secret_here ZEWSTID_DOMAIN=auth.zewstid.com
Complete Express Example
import express from 'express'; import { requireAuth, requireRole, optionalAuth } from '@zewstid/node/express'; const app = express(); // Configuration const authConfig = { domain: process.env.ZEWSTID_DOMAIN || 'auth.zewstid.com', clientId: process.env.ZEWSTID_CLIENT_ID!, debug: process.env.NODE_ENV === 'development', }; // Public route app.get('/public', (req, res) => { res.json({ message: 'Public access' }); }); // Optional auth (personalized if authenticated) app.get('/welcome', optionalAuth(authConfig), (req, res) => { if (req.user) { res.json({ message: `Welcome back, ${req.user.name}!` }); } else { res.json({ message: 'Welcome, visitor!' }); } } ); // Protected route app.get('/profile', requireAuth(authConfig), (req, res) => { res.json({ user: req.user, token: req.token, }); } ); // Admin only app.get('/admin', requireAuth(authConfig), requireRole('admin'), (req, res) => { res.json({ message: 'Admin dashboard' }); } ); // Multiple roles (OR logic) app.get('/moderator', requireAuth(authConfig), requireRole(['admin', 'moderator']), (req, res) => { res.json({ message: 'Moderator tools' }); } ); // API endpoint with token validation app.post('/api/data', requireAuth(authConfig), async (req, res) => { // req.user is available // req.token contains the access token // Your business logic here res.json({ success: true, userId: req.user?.id, }); } ); // Error handling app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: 'Internal server error' }); }); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); });
Fastify Example
Works with Fastify too:
import Fastify from 'fastify'; import { ZewstID } from '@zewstid/node'; const fastify = Fastify(); const zewstid = new ZewstID({ clientId: process.env.ZEWSTID_CLIENT_ID!, }); // Decorator for authentication fastify.decorateRequest('user', null); // Auth hook fastify.addHook('onRequest', async (request, reply) => { const authHeader = request.headers.authorization; if (!authHeader) { return reply.code(401).send({ error: 'No token' }); } const token = authHeader.split(' ')[1]; try { request.user = await zewstid.getUser(token); } catch (error) { return reply.code(401).send({ error: 'Invalid token' }); } }); fastify.get('/protected', async (request, reply) => { return { user: request.user }; }); fastify.listen({ port: 3000 });
Service Accounts (M2M)
ZewstIDServiceAccount Class
ZewstIDServiceAccountFor machine-to-machine authentication using the
client_credentialsConstructor:
import { ZewstIDServiceAccount } from '@zewstid/node'; const serviceAccount = new ZewstIDServiceAccount({ domain?: string; // Default: "auth.zewstid.com" clientId: string; // Service account client ID clientSecret: string; // Service account client secret scopes?: string[]; // Requested scopes tokenEndpoint?: string; // Override token endpoint cacheDuration?: number; // Token cache in seconds (default: 3300) });
Methods:
getToken(): Promise<string>
getToken(): Promise<string>Get an access token. Tokens are cached and automatically refreshed before expiration.
const token = await serviceAccount.getToken(); // Use the token for M2M API calls const res = await fetch('https://api.zewstid.com/api/v1/m2m/users', { headers: { Authorization: `Bearer ${token}` }, });
revokeToken(): Promise<void>
revokeToken(): Promise<void>Revoke the current cached token.
await serviceAccount.revokeToken();
clearCache(): void
clearCache(): voidClear the cached token without revoking it server-side.
serviceAccount.clearCache();
Complete Service Account Example
import { ZewstIDServiceAccount } from '@zewstid/node'; const sa = new ZewstIDServiceAccount({ clientId: process.env.ZEWSTID_SA_CLIENT_ID!, clientSecret: process.env.ZEWSTID_SA_CLIENT_SECRET!, scopes: ['users:read', 'users:write'], }); // List users async function listUsers() { const token = await sa.getToken(); const res = await fetch('https://api.zewstid.com/api/v1/m2m/users', { headers: { Authorization: `Bearer ${token}` }, }); return res.json(); } // Create a user async function createUser(email: string, firstName: string, lastName: string) { const token = await sa.getToken(); const res = await fetch('https://api.zewstid.com/api/v1/m2m/users', { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ email, firstName, lastName }), }); return res.json(); }
TypeScript Interfaces
import type { ZewstIDServiceAccountConfig, ServiceAccountToken, } from '@zewstid/node'; interface ZewstIDServiceAccountConfig { domain?: string; clientId: string; clientSecret: string; scopes?: string[]; tokenEndpoint?: string; cacheDuration?: number; } interface ServiceAccountToken { access_token: string; token_type: string; expires_in: number; scope: string; }
Agent Authentication
ZewstIDAgent Class
ZewstIDAgentExtends
ZewstIDServiceAccountConstructor:
import { ZewstIDAgent } from '@zewstid/node'; const agent = new ZewstIDAgent({ domain?: string; // Default: "auth.zewstid.com" clientId: string; // Agent client ID clientSecret: string; // Agent client secret agentId: string; // Agent identifier scopes?: string[]; // Requested scopes });
Methods:
Inherits all methods from
ZewstIDServiceAccountgetToken()revokeToken()clearCache()requestDelegation(options): Promise<Delegation>
requestDelegation(options): Promise<Delegation>Request delegation from a user.
const delegation = await agent.requestDelegation({ userId: 'user_123', scopes: ['profile:read', 'orders:read'], reason: 'Access your order history for support', expiresIn: 3600, // seconds }); console.log(delegation.delegationId); // "del_abc123" console.log(delegation.status); // "pending"
listDelegations(options?): Promise<Delegation[]>
listDelegations(options?): Promise<Delegation[]>List delegations for this agent.
const delegations = await agent.listDelegations({ status: 'approved', // "pending" | "approved" | "revoked" | "expired" }); for (const del of delegations) { console.log(`${del.delegationId}: ${del.userId} (${del.status})`); }
exchangeDelegation(delegationId): Promise<string>
exchangeDelegation(delegationId): Promise<string>Exchange an approved delegation for a delegated access token.
const delegatedToken = await agent.exchangeDelegation('del_abc123'); // Use to access resources on behalf of the user const profile = await fetch('https://api.zewstid.com/api/v1/m2m/users/user_123', { headers: { Authorization: `Bearer ${delegatedToken}` }, });
exchangeA2AToken(options): Promise<string>
exchangeA2AToken(options): Promise<string>Exchange your agent token for an audience-restricted token targeting another agent.
const exchangedToken = await agent.exchangeA2AToken({ targetAgentId: 'agent_b', scopes: ['data:read'], }); // Call Agent B's API const data = await fetch('https://agent-b.example.com/api/data', { headers: { Authorization: `Bearer ${exchangedToken}` }, });
Complete Agent Example
import { ZewstIDAgent } from '@zewstid/node'; const agent = new ZewstIDAgent({ clientId: process.env.AGENT_CLIENT_ID!, clientSecret: process.env.AGENT_CLIENT_SECRET!, agentId: 'agent_support_bot', }); // 1. Get M2M token for direct API access const token = await agent.getToken(); // 2. Request delegation from a user const delegation = await agent.requestDelegation({ userId: 'user_456', scopes: ['profile:read'], reason: 'View your profile to personalize support', expiresIn: 1800, }); // 3. After user approves, exchange for delegated token if (delegation.status === 'approved') { const userToken = await agent.exchangeDelegation(delegation.delegationId); // Access user resources with delegated permissions } // 4. Call another agent via A2A exchange const a2aToken = await agent.exchangeA2AToken({ targetAgentId: 'agent_analytics', scopes: ['analytics:read'], });
TypeScript Interfaces
import type { ZewstIDAgentConfig, Delegation, DelegationRequest, A2AExchangeRequest, } from '@zewstid/node'; interface ZewstIDAgentConfig extends ZewstIDServiceAccountConfig { agentId: string; } interface DelegationRequest { userId: string; scopes: string[]; reason: string; expiresIn?: number; } interface Delegation { delegationId: string; agentId: string; userId: string; scopes: string[]; status: 'pending' | 'approved' | 'revoked' | 'expired'; reason: string; expiresAt: string; createdAt: string; } interface A2AExchangeRequest { targetAgentId: string; scopes?: string[]; }
Agent Hardening (v0.2.0)
Sprint 11 adds kill switch resilience, human-in-the-loop approval workflows, just-in-time privileges, rich authorization requests (RFC 9396), workload identity federation, and intent metadata tracing.
Kill Switch & AgentKilledError
AgentKilledErrorWhen an agent is killed via the admin API, all token requests throw
AgentKilledErrorimport { ZewstIDAgent, AgentKilledError } from '@zewstid/node'; const agent = new ZewstIDAgent({ clientId: process.env.AGENT_CLIENT_ID!, clientSecret: process.env.AGENT_CLIENT_SECRET!, agentId: 'agent_support_bot', }); try { const token = await agent.getToken(); } catch (err) { if (err instanceof AgentKilledError) { // Agent has been terminated — stop all work console.error('Agent killed:', err.message); process.exit(1); } }
HITL (Human-in-the-Loop)
requestApproval(options): Promise<ApprovalRequest>
requestApproval(options): Promise<ApprovalRequest>Request human approval before performing a sensitive action:
const approval = await agent.requestApproval({ action: 'delete_user', resource: 'users/user_456', context: { reason: 'Account cleanup', ticketId: 'SUP-1234' }, }); console.log(approval.requestId); // "hitl_req_abc123" console.log(approval.status); // "pending_approval"
getApprovalStatus(requestId): Promise<ApprovalStatus>
getApprovalStatus(requestId): Promise<ApprovalStatus>Poll for approval resolution:
let status = await agent.getApprovalStatus(approval.requestId); while (status.status === 'pending_approval') { await new Promise(r => setTimeout(r, 2000)); status = await agent.getApprovalStatus(approval.requestId); } if (status.status === 'approved') { // Proceed with the action } else { console.log('Denied:', status.reason); }
JIT (Just-In-Time) Privileges
requestJIT(options): Promise<JITGrant>
requestJIT(options): Promise<JITGrant>Request short-lived elevated privileges:
const grant = await agent.requestJIT({ action: 'read_pii', resource: 'users/user_789/profile', duration: 300, // seconds (max 3600) authorizationDetails: [ { type: 'pii_access', actions: ['read'], dataTypes: ['email', 'phone'] }, ], }); console.log(grant.grantId); // "jit_grant_xyz" console.log(grant.status); // "active" or "pending_approval"
getJITStatus(grantId): Promise<JITGrant>
getJITStatus(grantId): Promise<JITGrant>Check grant status:
const status = await agent.getJITStatus(grant.grantId); // status.status: "active" | "pending_approval" | "expired" | "revoked" | "denied"
RAR (Rich Authorization Requests)
getTokenWithRAR(authorizationDetails): Promise<string>
getTokenWithRAR(authorizationDetails): Promise<string>Request a token scoped with RFC 9396 authorization details:
const token = await agent.getTokenWithRAR([ { type: 'payment_transfer', actions: ['initiate'], locations: ['us'], amount: 500, currency: 'USD', recipient: 'merchant_abc', }, ]); // Token is bound to the exact authorization details
The server validates the request against the registered JSON Schema and the agent's capability ceiling.
Intent Metadata
setIntent(intent): void
setIntent(intent): voidSet intent headers for all subsequent API calls:
agent.setIntent({ action: 'process_refund', reason: 'Customer requested refund for order #1234', initiator: 'support_agent', taskId: 'task_abc123', chainId: 'chain_xyz789', });
getIntent(): Intent | null
getIntent(): Intent | nullGet the current intent:
const intent = agent.getIntent(); // { action, reason, initiator, taskId, chainId }
clearIntent(): void
clearIntent(): voidClear intent headers:
agent.clearIntent();
Workload Identity Federation
Configure secretless authentication via the
workloadIdentityconst agent = new ZewstIDAgent({ clientId: 'agent-payment-bot', agentId: 'agent_payment_bot', workloadIdentity: { platform: 'kubernetes', // 'kubernetes' | 'aws' | 'gcp' | 'azure' tokenPath: '/var/run/secrets/tokens/zewstid-token', }, }); // No client secret needed const token = await agent.getToken();
Platform options:
| Platform | Config |
|---|---|
kubernetes | tokenPath |
aws | roleArn |
gcp | serviceAccountEmail |
azure | clientId |
TypeScript Interfaces
import type { ApprovalRequest, ApprovalStatus, JITGrant, JITRequest, Intent, WorkloadIdentityConfig, } from '@zewstid/node'; interface ApprovalRequest { requestId: string; status: 'pending_approval' | 'approved' | 'denied' | 'expired'; expiresAt: string; } interface JITGrant { grantId: string; status: 'active' | 'pending_approval' | 'expired' | 'revoked' | 'denied'; scopes: string[]; expiresAt: string; } interface Intent { action: string; reason: string; initiator: string; taskId?: string; chainId?: string; } interface WorkloadIdentityConfig { platform: 'kubernetes' | 'aws' | 'gcp' | 'azure'; tokenPath?: string; roleArn?: string; serviceAccountEmail?: string; clientId?: string; }
Examples
FAQ
Q: How do I get the access token from my frontend? A: Your frontend SDK (@zewstid/react, @zewstid/nextjs) will send it in the
Authorization: Bearer <token>Q: Do I need client secret? A: Only for token introspection and revocation. JWT validation works without it.
Q: How often are JWKS keys refreshed? A: Keys are cached for 1 hour by default. Configure with
jwksCacheDurationQ: Can I use this without Express? A: Yes! Use the
ZewstIDSupport
License
Proprietary and Confidential
Copyright © 2025 Zewst, Inc. All Rights Reserved.
This software is proprietary and confidential. Unauthorized distribution or use is prohibited.
Was this page helpful?
Let us know how we can improve our documentation