Skip to content
oryPublic

About

SAML 2.0 parser for Node.js

Topics

Resources

Security policy

Stars

16 stars

Watchers

0 watching

Forks

Repository files navigation

SAML 2.0 & 1.1 Assertion Parser & Validator

Build Status

=============

boxyhq/saml20 is a fork of a fork of saml20. It now has extended functionality and diverges from the original unmaintained library. The new package is published here - https://www.npmjs.com/package/@boxyhq/saml20

Installation

$ npm install @boxyhq/saml20

Usage

parse() and validate() return promises. Before 1.6.0 they took a callback as their last argument; since 1.6.0 a callback is ignored and is never called, so await the result or use .then()/.catch() instead.

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 (optional): the expected audience, or a list of accepted ones. When set, every AudienceRestriction in the assertion must name one of them.
  • strictAudienceValidation (optional): require an exact audience match. Without it, an audience that merely starts with the expected value is accepted.
  • 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. When set, the signed content must name it: 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.
  • 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',
  strictAudienceValidation: true,
  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);

[DEPRECATED] saml.parse(rawResponse)

Resolves with the same profile as validate(), but checks nothing: not the signature, expiry, audience or anything else. Anyone can forge its output, so never use it to authenticate a user. Use validate() for that, and parseIssuer() to read the Issuer before validating.

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

Verifies the XML signature on xml and returns the signed XML, or throws / returns null when the signature is invalid. 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',
  ],
});

Tests

$ npm test            # mocha with coverage
$ npm run typecheck   # type-checks lib/ and test/
$ npm run lint

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.

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.

Community

  • Discord (For live discussion with the Community and BoxyHQ team)
  • Twitter (Get the news fast)

Reporting Security Issues

Responsible Disclosure

License

MIT

About

SAML 2.0 parser for Node.js

Topics

Resources

Security policy

Stars

16 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages