{"_id":"@antzsoft/wso2-auth-backend","name":"@antzsoft/wso2-auth-backend","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@antzsoft/wso2-auth-backend","version":"1.0.0","description":"Node.js backend SDK for Antz Central User Service (WSO2 IS 7.2.0) — M2M SCIM2 user management, JWT verification, admin & self-service password flows.","main":"./dist/cjs/index.js","module":"./dist/esm/index.js","types":"./dist/esm/index.d.ts","exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}}},"engines":{"node":">=18"},"scripts":{"build":"npm run build:esm && npm run build:cjs","build:esm":"tsc -p tsconfig.esm.json && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({type:'module'}))\"","build:cjs":"tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({type:'commonjs'}))\"","dev":"tsc -p tsconfig.esm.json --watch"},"dependencies":{"jose":"^5.9.6"},"devDependencies":{"@types/node":"^26.1.2","typescript":"^5.4.5"},"keywords":["wso2","scim","scim2","oauth2","jwt","jwks","identity","antz","backend"],"license":"MIT","_id":"@antzsoft/wso2-auth-backend@1.0.0","gitHead":"d5806dea98363ad1b6e1993b918fe3a0e057ea9c","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-Mm8RN+2+0xK2o+IeTUrYB2f+p8lU+wdr10mTdhMF51BWywwD7dP7+Lc8ZvaTZCrIGgGwidhzz7q4zYuZ3JCvmw==","shasum":"929d12f5dd7e46cfa76bf2dd22b38d4efc96836d","tarball":"https://registry.npmjs.org/@antzsoft/wso2-auth-backend/-/wso2-auth-backend-1.0.0.tgz","fileCount":84,"unpackedSize":205489,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEIaNQT9mKpKKnpANjgMC/oFjlKDX3tXDTerDHxpOA/0AiA4h2GmVU63mnFqNTzP/qwdHi29QzuEm47mCipQueTAgA=="}]},"_npmUser":{"name":"antz.karthik","email":"karthikmitta@lifesciencetrust.com"},"directories":{},"maintainers":[{"name":"antz.karthik","email":"karthikmitta@lifesciencetrust.com"},{"name":"antzsoft_trellisys","email":"antzsoft@lifesciencetrust.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/wso2-auth-backend_1.0.0_1785680466698_0.852722075155594"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-02T14:21:06.470Z","1.0.0":"2026-08-02T14:21:06.847Z","modified":"2026-08-02T14:21:07.120Z"},"maintainers":[{"name":"antz.karthik","email":"karthikmitta@lifesciencetrust.com"},{"name":"antzsoft_trellisys","email":"antzsoft@lifesciencetrust.com"}],"description":"Node.js backend SDK for Antz Central User Service (WSO2 IS 7.2.0) — M2M SCIM2 user management, JWT verification, admin & self-service password flows.","keywords":["wso2","scim","scim2","oauth2","jwt","jwks","identity","antz","backend"],"license":"MIT","readme":"# @antzsoft/wso2-auth-backend\n\nNode.js backend SDK for **Antz Central User Service** (WSO2 IS 7.2.0). Wraps the\nmachine-to-machine (`client_credentials`) SCIM2 user-management API, admin and\nself-service password flows, local JWT verification via JWKS, and OIDC\nsession/logout helpers — everything a backend needs to integrate without\nhand-rolling `fetch` calls against WSO2 directly.\n\nCompanion to [`@antzsoft/wso2-auth-web`](../antz-wso2-auth-web) (browser/frontend)\nand [`@antzsoft/wso2-auth-reactnative`](../antz-wso2-auth-reactnative) — this\npackage is the **server-side** counterpart, built from the same API surface\ndocumented in [`docs/backend-api-integration-guide.md`](../../docs/backend-api-integration-guide.md).\n\n## Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n- [User Management](#user-management)\n- [Bulk Update & Delete](#bulk-update--delete)\n- [Password Flows](#password-flows)\n- [JWT Verification](#jwt-verification)\n- [Session Helpers](#session-helpers)\n- [Error Types](#error-types)\n- [Environment Routing](#environment-routing)\n\n## Installation\n\n```bash\nnpm install @antzsoft/wso2-auth-backend\n```\n\nRequires Node.js 18+ (uses the global `fetch`). Ships as dual ESM + CommonJS —\n`import` and `require()` both work.\n\n## Quick Start\n\n```ts\nimport { AntzBackendClient } from \"@antzsoft/wso2-auth-backend\";\n\nconst antz = new AntzBackendClient({\n  baseUrl: process.env.WSO2_BASE_URL!,       // e.g. https://auth.antzsystems.com\n  tenant: process.env.WSO2_TENANT!,          // \"prod\" | \"dev\" | \"uat\"\n  clientId: process.env.WSO2_CLIENT_ID!,\n  clientSecret: process.env.WSO2_CLIENT_SECRET!,\n});\n\nconst user = await antz.users.createUser({\n  userName: \"john@example.com\",\n  email: \"john@example.com\",\n  givenName: \"John\",\n  familyName: \"Doe\",\n  phone: \"+919876543210\",\n});\n```\n\nThe M2M token is fetched lazily on the first call and cached in memory\n(refreshed ~60s before it expires) — you never need to manage it yourself.\n\n## Configuration\n\n```ts\ninterface AntzBackendConfig {\n  baseUrl: string;                    // \"https://auth.antzsystems.com\"\n  tenant: string;                     // \"prod\" | \"dev\" | \"uat\"\n  clientId: string;\n  clientSecret: string;\n  scope?: string;                     // defaults to the full user-mgt scope set\n  audience?: string;                  // expected `aud` claim on verified tokens; omit to skip the check\n  tokenRefreshMarginSeconds?: number; // default 60\n  jwksCacheMaxAgeMs?: number;         // default 24h\n}\n```\n\nStore `clientId`/`clientSecret` in your secrets manager, not in source or\nplain `.env` files committed to the repo.\n\n## User Management\n\n`antz.users` — see [Section 3–5 of the integration guide](../../docs/backend-api-integration-guide.md) for full request/response shapes.\n\n```ts\n// Create — omit `password` to use the auto-generated-password + SMS/email notification flow\nawait antz.users.createUser({ userName: \"alice\", phone: \"+919876543210\" });\n\n// Lookup — email/username use SCIM filters; phone uses a dedicated lookup endpoint\nconst { exists, user } = await antz.users.validateUserExists(\"email\", \"john@example.com\");\nconst byPhone = await antz.users.validateUserExists(\"phone\", \"+919876543210\");\n\nawait antz.users.getUser(wso2UserId);\nawait antz.users.listUsers({ startIndex: 1, count: 20 }); // no sortBy — unsupported by WSO2 IS 7.2.0\n\n// Update — only supplied fields change\nawait antz.users.updateUser(wso2UserId, { active: false });                 // deactivate\nawait antz.users.updateUser(wso2UserId, { unlockAccount: true });           // unlock after failed logins\nawait antz.users.updateUser(wso2UserId, { antzzooids: [\"ZOO-001\"] });       // replace the full Zoo ID list\nawait antz.users.updateUser(wso2UserId, { addAntzzooids: [\"ZOO-003\"] });    // append without removing existing\n\nawait antz.users.deleteUser(wso2UserId); // permanent — prefer { active: false } in most cases\n```\n\n`antzuserid` is single-value; `antzzooids` is multi-value — pass an array even\nfor one Zoo ID, and use `addAntzzooids` (SCIM `op: add`) instead of\n`antzzooids` (SCIM `op: replace`) when you want to append rather than\noverwrite.\n\n## Bulk Update & Delete\n\nWSO2's SCIM2 Bulk API batches independent per-user operations into one HTTP\ncall. **Not atomic** — already-applied operations are not rolled back if a\nlater one fails. You must already know the target `wso2-uuid`s.\n\n```ts\nconst results = await antz.users.bulkUpdateUsers([\n  { id: wso2UserId1, active: false },\n  { id: wso2UserId2, email: \"new2@example.com\" },\n]);\n// results: [{ bulkId, ok, code, detail? }, ...] — check each entry, an overall success does not imply every op succeeded\n\nawait antz.users.bulkDeleteUsers([wso2UserId1, wso2UserId2]); // permanent\n```\n\n## Password Flows\n\n`antz.password`\n\n```ts\n// Admin reset — silent, no notification\nawait antz.password.adminResetPassword(wso2UserId, \"NewPassword123!\");\n\n// Admin reset — WSO2 notifies the user via SMS/email with the new password\n// (requires the M2M token to be JWT, not opaque)\nawait antz.password.adminResetPasswordAndNotify(wso2UserId, \"NewPassword123!\");\n\n// Self-service — requires the USER's own Bearer token, never the M2M token\nconst otpStatus = await antz.password.sendChangePasswordOtp(userAccessToken);\nif (otpStatus.ok || otpStatus.code === \"OTP_NOT_ENABLED\") {\n  const result = await antz.password.changePassword(\n    userAccessToken,\n    \"OldPassword123!\",\n    \"NewPassword456@\",\n    otpStatus.ok ? \"123456\" : undefined,\n  );\n}\n\n// Or throw instead of returning a result object:\nawait antz.password.changePasswordOrThrow(userAccessToken, \"OldPassword123!\", \"NewPassword456@\");\n```\n\n## JWT Verification\n\n`antz.jwt` — local verification against WSO2's JWKS, no network call per\nrequest. The JWKS response is cached (default 24h) and refetched automatically\non a `kid` cache miss (key rotation).\n\n```ts\ntry {\n  const claims = await antz.jwt.verifyAccessToken(bearerToken);\n  // claims.sub, claims.email, claims.phone_number, claims.scope, ...\n} catch (err) {\n  if (err instanceof AntzTokenVerificationError) {\n    // err.code: \"TOKEN_EXPIRED\" | \"INVALID_SIGNATURE\" | \"CLAIM_MISMATCH\" | \"INVALID_TOKEN\"\n  }\n}\n\n// Reshaped into common fields:\nconst verified = await antz.jwt.extractVerifiedClaims(bearerToken);\n// { wso2Id, email, phone, firstName, lastName, username, tenant, scopes, issuedAt, expiresAt, clientId }\n```\n\n### Express middleware example\n\n```ts\nasync function authMiddleware(req, res, next) {\n  const header = req.headers.authorization ?? \"\";\n  const token = header.startsWith(\"Bearer \") ? header.slice(7) : null;\n  if (!token) return res.status(401).json({ code: \"MISSING_TOKEN\" });\n\n  try {\n    req.user = await antz.jwt.verifyAccessToken(token);\n    next();\n  } catch (err) {\n    const code = err instanceof AntzTokenVerificationError ? err.code : \"INVALID_TOKEN\";\n    res.status(401).json({ code });\n  }\n}\n```\n\n### Decode without verifying (debugging only)\n\n```ts\nimport { decodeTokenUnsafe } from \"@antzsoft/wso2-auth-backend\";\ndecodeTokenUnsafe(token); // no signature check — never use for authorization decisions\n```\n\n### Introspection (opaque tokens only)\n\n```ts\nconst result = await antz.jwt.introspect(token, clientId, clientSecret);\nif (!result.active) { /* expired or revoked */ }\n```\n\nPrefer `verifyAccessToken` for JWTs — introspection costs a network round-trip\nto WSO2 on every call.\n\n## Session Helpers\n\n`antz.session` and the standalone `assertExpectedUser` guard — for backends\nthat drive the OIDC flow directly (no Antz frontend SDK), e.g. server-rendered\nintegrations like odoo or ThingsBoard.\n\n### Cross-app SSO mismatch guard\n\nWSO2 keeps one login session per browser (`commonAuthId`), shared across every\napp. A second app's `/authorize` call can silently get back a token for\nwhichever user is already signed in elsewhere — regardless of which username\nthat app just collected and verified. Call this right after exchanging the\nauthorization code, passing the username your app expected:\n\n```ts\nimport { assertExpectedUser, AntzSessionUserMismatchError } from \"@antzsoft/wso2-auth-backend\";\n\ntry {\n  assertExpectedUser(tokens.id_token, expectedUsername);\n} catch (err) {\n  if (err instanceof AntzSessionUserMismatchError) {\n    // tell the user to log out of the other app first\n  }\n}\n```\n\nFail-open by design — a no-op if there's nothing to compare (e.g. the\n`id_token`'s `sub` is a bare UUID and no `email`/`username` claim is\nconfigured on the WSO2 application).\n\n### Logout — local vs. full\n\n```ts\n// Local (app-only): revoke this app's refresh token; other apps stay signed in\nawait antz.session.revokeToken(refreshToken);\n\n// Full (SSO-wide), back-channel: revoke + end the WSO2 session record\nawait antz.session.fullLogout(refreshToken, idToken);\n\n// Full, front-channel: redirect the user's browser (required to clear the commonAuthId cookie)\nconst logoutUrl = antz.session.buildFrontChannelLogoutUrl(idToken, postLogoutRedirectUri);\nres.redirect(logoutUrl);\n```\n\n## Error Types\n\nAll errors extend `AntzAuthError`:\n\n| Class | Thrown when |\n|---|---|\n| `AntzTokenError` | M2M token request failed |\n| `AntzApiError` | Any non-2xx SCIM/REST response (carries `status`/`body`) |\n| `AntzUserExistsError` | `createUser` hit a 409 conflict |\n| `AntzUserNotFoundError` | A single-user endpoint returned 404 |\n| `AntzTokenVerificationError` | JWT verification failed (`.code`: `TOKEN_EXPIRED` / `INVALID_SIGNATURE` / `CLAIM_MISMATCH` / `INVALID_TOKEN` / `MISSING_TOKEN`) |\n| `AntzSessionUserMismatchError` | Cross-app SSO session guard tripped |\n| `AntzChangePasswordError` | `changePasswordOrThrow` failed (`.code`, `.status`) |\n\n## Environment Routing\n\nSet `WSO2_TENANT` (`prod` \\| `dev` \\| `uat`) as an environment variable so no\ncode changes are needed when promoting across environments. Users are fully\nisolated per tenant — never mix tenant configs within a single backend\ndeployment.\n","readmeFilename":"README.md","_rev":"1-c98d998bbec765804a86a60d4e8b92bf"}