@boxyhq/saml20

中文文档
3.1.0发布于 2026/11/06MITTypeScriptCommonJS构建来源已验证近 30 天下载 56.4万

SAML 2.0 token parser for Node.js

原始使用文档
文档目录10 节
  1. Installation
  2. Usage
  3. saml.validate(rawResponse, options)
  4. saml.parseIssuer(rawResponse)
  5. saml.validateSignature(xml, publicKey, thumbprint, options?)
  6. Upgrading to 2.0
  7. Tests
  8. Contributing
  9. Reporting Security Issues
  10. License

SAML 2.0 parser and validator

Build Status

A SAML 2.0 library for Node.js: it validates SAML Responses, and creates and parses AuthnRequests, logout messages and metadata. It started as a fork of saml20 and has since diverged from it. Ory maintains it, and it is published on npm as @boxyhq/saml20.

Installation

$ npm install @boxyhq/saml20

Usage

validate() returns a promise.

saml.validate(rawResponse, options)

Verifies a SAML Response (or a lone Assertion) and resolves with the user's profile. It rejects when anything fails: an unsuccessful StatusCode, a missing or invalid signature, an expired assertion, or a failed audience, InResponseTo, Recipient or replay check.

rawResponse is the XML as a string (decode the base64 SAMLResponse form field first).

options (TypeScript: ValidateOptions):

  • publicKey: the trusted IdP signing certificate, as base64 DER or PEM. Several certificates (for key rotation) are separated by commas.
  • thumbprint: the SHA-1 thumbprint(s) of a trusted certificate, comma-separated. The certificate itself is taken from the KeyInfo of the signature being verified. Pass either publicKey or thumbprint, not both.
  • privateKey (optional): decrypts an EncryptedAssertion.
  • audience: the expected audience, or a list of accepted ones. Every AudienceRestriction in the assertion must name one of them exactly. Fails with Invalid audience. A missing or empty value fails with audience is required.
  • inResponseTo (optional): the ID of the AuthnRequest this response answers. When set, the signed content must carry the same InResponseTo; a missing value fails. Fails with Invalid InResponseTo.
  • recipient (optional). The ACS URL the response was posted to, or a list of ACS URLs it may have been posted to (for an SP that accepts responses at several addresses). When set, the signed content must name it (one of them, each candidate judged on its own): a signed Response/@Destination must equal it, when any bearer SubjectConfirmationData carries a Recipient, one bearer confirmation must both name it and be within its own NotBefore/NotOnOrAfter window (so NotOnOrAfter is required on it; bypassExpiration skips this window check but not the Recipient match), and at least one of the two must be present. An empty Recipient counts as present and never matches. The unsigned Response wrapper is never used. Fails with Invalid Recipient. An empty string, an empty list or a list with an empty entry fails with recipient must be a non-empty URL or a non-empty list of them.
  • assertionReplayValidator (optional): (info) => boolean | Promise<boolean>, called after every other check passes, with { assertionId, sessionIndex, notOnOrAfter, inResponseTo } from the signed assertion. Return true if the assertion was already used, and the response is rejected as a replay. Keep each entry until at least notOnOrAfter plus 10 minutes, the clock skew validate() allows.
  • bypassExpiration (optional). This flag indicates expiration validation bypass (useful for testing, not recommended in production environments);
  • allowedSignatureAlgorithms / allowedHashAlgorithms (optional). Algorithm allowlists forwarded to validateSignature, see below.
  • maxSignatureReferences (optional). Ceiling on SignedInfo/Reference elements, forwarded to validateSignature, see below.
const saml = require('@boxyhq/saml20').default;

const profile = await saml.validate(rawResponse, {
  publicKey: 'MIICDzCCAXygAwIBAgIQVWXAvbbQyI5Bc...',
  audience: 'https://sp.example.com',
  inResponseTo: requestId,
  recipient: 'https://sp.example.com/acs',
});

const { claims, issuer, sessionIndex, assertionId, notOnOrAfter } = profile;

Errors are Errors with a fixed message. When the cause is more specific (for example, why a signature failed), the error is a WrapError and the cause is in error.inner.

saml.parseIssuer(rawResponse)

Returns the Issuer of a SAML Response without validating anything. Use it to find out which IdP a response claims to come from, so you can pick the certificate to pass to validate().

const issuer = saml.parseIssuer(rawResponse);

saml.validateSignature(xml, publicKey, thumbprint, options?)

Verifies the XML signature on xml and returns the signed XML. Read the document from that return value, not from xml: only the signed content is covered. It throws when the signature is invalid or cannot be checked; a signature that does not verify fails with invalid signature: the signature does not verify. Pass either publicKey (one certificate, or several separated by commas for key rotation) or thumbprint, not both.

options (optional) restricts which algorithms are accepted:

  • allowedSignatureAlgorithms — accepted SignatureMethod URIs.
  • allowedHashAlgorithms — accepted DigestMethod URIs.

When an allowlist is omitted, the default set from xml-crypto applies (RSA-SHA1, RSA-SHA256, RSA-SHA512 and SHA-1, SHA-256, SHA-512 digests). When one is given, a document using any other algorithm is rejected; an empty list rejects everything.

  • maxSignatureReferences — maximum number of Reference elements accepted in the signature's SignedInfo (default 16). A SAML signature carries exactly one. The ceiling bounds the work xml-crypto does before it verifies SignatureValue: every Reference is canonicalized first, so an unbounded list is an unauthenticated CPU denial of service. A document over the ceiling is rejected with invalid signature: SignedInfo declares N References; at most M are allowed, before any canonicalization.
var saml = require('@boxyhq/saml20').default;

var signedXml = saml.validateSignature(xml, publicKey, null, {
  allowedSignatureAlgorithms: [
    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512',
  ],
  allowedHashAlgorithms: [
    'http://www.w3.org/2001/04/xmlenc#sha256',
    'http://www.w3.org/2001/04/xmlenc#sha512',
  ],
});

Upgrading to 2.0

2.0 removes APIs that were deprecated or that Polis no longer uses:

  • Only the package root can be imported. package.json now has an exports map, so paths inside the package such as @boxyhq/saml20/dist/typings fail with ERR_PACKAGE_PATH_NOT_EXPORTED (and with TS2307 under moduleResolution node16, nodenext or bundler). Import from the root instead, for example import type { SAMLProfile } from '@boxyhq/saml20'. Everything the root exports is listed in test/lib/compat.spec.ts.
  • parse() is gone. It returned a profile without checking the signature, expiry, audience or anything else, so its output could be forged. Use validate() to authenticate a response, and parseIssuer() to read the Issuer before validating (for example, to pick the IdP certificate).
  • decryptXml and PubKeyInfo are no longer exported from the package root. They were internal helpers for validate() and sign().
  • hasValidSignature is removed. Use validateSignature(), which also rejects being given both a certificate and a thumbprint.
  • isMultiRootedXMLError is removed. A multi-rooted document is rejected with the error multirooted xml not allowed.
  • strictAudienceValidation is gone and audiences are always compared exactly. Before 2.0, without that option an Audience that merely started with the expected value was accepted (https://sp.example.com/metadata.evil.example passed for https://sp.example.com/metadata). If you relied on a prefix match, pass every accepted audience in audience instead.
  • validateSignature() throws when the signature does not verify, where it used to return null. It now either returns the signed XML or throws.
  • audience is required. Before 2.0, leaving it out skipped the audience check, so an assertion issued for any other service provider that trusts the same IdP was accepted.
  • parseMetadata(), parseSAMLRequest(), parseLogoutRequest() and parseLogoutResponse() parse with the same XML parser as validate() (@xmldom/xmldom) instead of xml2js. They now reject a document with more than one root element. A namespace prefix the document uses without declaring it is still accepted (prefixes play no part in the result), as is a certificate element that carries attributes. Text and attribute values get the line-ending and whitespace normalization XML requires, so a certificate pasted with CRLF line endings comes back with LF in publicKey (its thumbprint is unchanged). A malformed document fails with the parser's message, for example missing root element instead of Non-whitespace before first tag., and an & that does not start a reference (a & b) fails with Invalid XML.
  • The XML that request(), createSAMLResponse(), createLogoutRequest(), createLogoutResponse(), createIdPMetadataXML() and createSPMetadataXML() produce is serialized by xmlbuilder2 instead of xmlbuilder. The documents are the same except that a namespace declaration already in scope from an ancestor is not repeated on a descendant, and an element with empty text is written as <a></a> rather than <a/>. A saml:Assertion from createSAMLResponse() still declares every prefix it uses, so it stays self-contained when extracted from its samlp:Response.

Tests

$ npm test            # node:test with coverage
$ npm run typecheck   # type-checks lib/ and test/
$ npm run lint       # oxlint

Fixtures live in test/assets. Tests that need a fresh validity window, or a shape the fixtures do not have, build and sign their own documents with the helpers in test/helpers/fixtures.ts.

Contributing

Thanks for taking the time to contribute! Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody and are appreciated.

Report bugs in GitHub issues. Please try to create bug reports that are:

  • Reproducible. Include steps to reproduce the problem.
  • Specific. Include as much detail as possible: which version, what environment, etc.
  • Unique. Do not duplicate existing opened issues.
  • Scoped to a Single Bug. One bug per report.

Reporting Security Issues

Responsible Disclosure

License

MIT

版本与发布记录

@boxyhq/saml20 共发布 121 个版本,首次发布于 2021/08/23;过去 12 个月发布了 21 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.1.0latest2026/11/062 天前
3.0.02026/11/053 天前
2.1.02026/11/035 天前
2.0.12026/11/026 天前
2.0.02026/10/2514 天前
1.22.02026/10/1226 天前

其他发布通道:beta → 1.5.1-beta.1723。

关于 @boxyhq/saml20 的常见问题

@boxyhq/saml20 是什么?

SAML 2.0 token parser for Node.js

如何安装 @boxyhq/saml20?

在项目目录运行 npm install @boxyhq/saml20,或使用 pnpm add / yarn add / bun add。

@boxyhq/saml20 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@boxyhq/saml20 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@boxyhq/saml20 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@boxyhq/saml20 可以商用吗?

@boxyhq/saml20 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@boxyhq/saml20 的下载量有多少?

根据 npm 官方下载统计,@boxyhq/saml20 在 2026/10/09 – 2026/11/07 这 30 天内被下载 563,573 次,日均约 18,786 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@boxyhq/saml20 还在维护吗?

最新版本 3.1.0 发布于 2026/11/06(2 天前);至今共发布 121 个版本,其中过去 12 个月发布了 21 个;首次发布于 2021/08/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。