wsfed-caep-nodejs

A WS-Federation relying party for server-side Node.js applications: the traditional web application that renders its pages on the server and keeps its session there. It covers:

  • The passive requestor profile of WS-Federation 1.2: wsignin1.0 (with wfresh, wauth, whr), the response POSTed to the reply URL, wsignout1.0 and wsignoutcleanup1.0.
  • SAML 2.0 assertions, asked for with a wreq RequestSecurityToken, in either WS-Trust shape (February 2005, or 1.3 in a collection). A SAML 1.1 token is refused.
  • Strict validation: the assertion’s signature always required, verified only against the identity provider’s metadata certificates and read from what was signed (no signature wrapping); audience, recipient, time window, wctx correlation, and each assertion accepted once.
  • Encryption: EncryptedAssertion, AD FS’s bare EncryptedData, EncryptedID and EncryptedAttribute decrypted with node:crypto (AES-GCM/CBC, RSA-OAEP; PKCS#1 v1.5 refused).
  • Federation metadata both ways: this relying party’s written from its configuration, the identity provider’s read from a file or a URL.
  • CAEP and RISC: an OpenID Shared Signals receiver that ends the application’s sessions when the identity provider revokes a session or disables an account, whether or not the browser is open.

It is TypeScript and works from JavaScript, needs no web framework, and depends on xml-crypto and @xmldom/xmldom only. It is built and tested against iya-sts’s WS-Federation identity provider.

Install

npm install wsfed-caep-nodejs

Where to read

Quick look

import { RelyingParty } from 'wsfed-caep-nodejs';

const rp = await RelyingParty.fromFile('wsfed.json');

// /login
const request = rp.login({ relayState: '/' });       // keep request.id with the browser
// redirect to request.url

// POST /wsfed (the reply URL)
const who = await rp.handleReply({ method, url, body }, { requestId });
console.log(who.nameId, who.attributes, who.sessionIndex);

From JavaScript: const { RelyingParty } = require('wsfed-caep-nodejs'), or the same import.

The configuration is three lines:

{
  "realm": "https://app.example.com/",
  "replyUrl": "https://app.example.com/wsfed",
  "idpMetadata": "https://idp.example.com/FederationMetadata/2007-06/FederationMetadata.xml"
}