hapi-saml2
raw JSON → 5.1.3 verified Sat Apr 25 auth: no javascript
hapi-saml2 is a Hapi.js plugin providing SAML-based Single Sign On via the node-saml library. Version 5.1.3 is current stable. It supports @hapi/hapi v18-v21, and requires @hapi/boom >=7.2.0 as a peer dependency. Key differentiators: built specifically for Hapi.js ecosystem (inline with Hapi's plugin architecture), replaces older passport-saml dependency with modern node-saml, and offers customizable route prefixes and handler hooks (preLogin, postResponseValidationErrorHandler). Release cadence: occasional updates to match node-saml releases.
Common errors
error Error: SAML instance is not configured ↓
cause getSAMLOptions returned undefined or threw inside the function.
fix
Ensure getSAMLOptions always returns an object with required node-saml properties (entryPoint, cert, etc.).
error AssertionError: login must be a function ↓
cause Login option is missing or not a function in plugin options.
fix
Add login: async (request, identifier, user) => { return true; } to options.
error TypeError: Cannot destructure property 'nameID' of 'user' as it is undefined ↓
cause user parameter in login callback is undefined if SAML response lacks expected profile attributes.
fix
Check that Identity Provider sends NameID; use identifier (string) instead of user properties if only NameID is needed.
Warnings
breaking Version 5.x replaced passport-saml with @node-saml/node-saml. Callback signatures changed: login now receives (request, identifier, user) instead of the old passport-based signature. ↓
fix Update login callback to accept (request, identifier, user) and return boolean or object.
breaking Removed samlOptions top-level config. Use getSAMLOptions function instead. ↓
fix Move SAML configuration into getSAMLOptions function that returns node-saml options.
deprecated Options like 'issuer', 'callbackUrl' directly in options root were removed. Must be inside getSAMLOptions return. ↓
fix Place all SAML-specific options inside the object returned by getSAMLOptions.
gotcha The plugin requires @hapi/hapi v18 or later. Hapi v17 is not supported (will throw on register). ↓
fix Upgrade @hapi/hapi to v18+ (tested up to v21).
gotcha The plugin modifies server routes automatically. If apiPrefix collides with existing routes, registration fails silently with Boom.conflict. ↓
fix Ensure apiPrefix (default '/saml') is not already used by other plugins or routes.
Install
npm install hapi-saml2 yarn add hapi-saml2 pnpm add hapi-saml2 Imports
- default wrong
import Hapi from '@hapi/hapi' (ESM may not work if Hapi is CommonJS)correctconst Hapi = require('@hapi/hapi'); const plugin = require('hapi-saml2'); await server.register({ plugin, options }) - plugin options (getSAMLOptions, login, logout) wrong
options: { samlOptions: { ... } } (no such top-level key; saml config must be returned from getSAMLOptions)correctoptions: { getSAMLOptions: (request) => { return { entryPoint: '...', cert: '...' }; }, login: async (request, identifier, user) => { return true; }, logout: async (request) => { } } - login callback wrong
login: async (request, identifier, user) => { /* no return */ } (must return boolean or { success: boolean, errorMessage?: string })correctlogin: async (request, identifier, user) => { return true; }
Quickstart
const Hapi = require('@hapi/hapi');
const server = Hapi.server({ port: 3000, host: 'localhost' });
const init = async () => {
await server.register({
plugin: require('hapi-saml2'),
options: {
getSAMLOptions: (request) => ({
entryPoint: process.env.SAML_ENTRY_POINT ?? 'https://idp.example.com/ssos',
cert: process.env.SAML_CERT ?? '',
issuer: 'http://localhost:3000',
callbackUrl: 'http://localhost:3000/saml/callback'
}),
login: async (request, identifier, user) => {
// After SAML response validation, identifier is the NameID
console.log(`User ${identifier} logging in`);
return true;
},
logout: async (request) => {
// Implement logout logic, e.g., clear session
console.log('User logged out');
},
redirectUrlAfterSuccess: '/dashboard',
redirectUrlAfterFailure: '/login'
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init().catch(console.error);