{"id":18409,"library":"hapi-saml2","title":"hapi-saml2","description":"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.","status":"active","version":"5.1.3","language":"javascript","source_language":"en","source_url":"https://github.com/toriihq/hapi-saml2","tags":["javascript"],"install":[{"cmd":"npm install hapi-saml2","lang":"bash","label":"npm"},{"cmd":"yarn add hapi-saml2","lang":"bash","label":"yarn"},{"cmd":"pnpm add hapi-saml2","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"peer dependency for HTTP-friendly error objects used in configuration error handling and SAML failures","package":"@hapi/boom","optional":false}],"imports":[{"note":"Package is CommonJS only; use require() with @hapi/hapi (CommonJS) to avoid interop issues.","wrong":"import Hapi from '@hapi/hapi' (ESM may not work if Hapi is CommonJS)","symbol":"default","correct":"const Hapi = require('@hapi/hapi'); const plugin = require('hapi-saml2'); await server.register({ plugin, options })"},{"note":"getSAMLOptions is a function that must return node-saml options; it is called per request to allow dynamic configuration.","wrong":"options: { samlOptions: { ... } } (no such top-level key; saml config must be returned from getSAMLOptions)","symbol":"plugin options (getSAMLOptions, login, logout)","correct":"options: { getSAMLOptions: (request) => { return { entryPoint: '...', cert: '...' }; }, login: async (request, identifier, user) => { return true; }, logout: async (request) => { } }"},{"note":"Return true to authenticate; return object with success: false to trigger postResponseValidationErrorHandler.","wrong":"login: async (request, identifier, user) => { /* no return */ } (must return boolean or { success: boolean, errorMessage?: string })","symbol":"login callback","correct":"login: async (request, identifier, user) => { return true; }"}],"quickstart":{"code":"const Hapi = require('@hapi/hapi');\nconst server = Hapi.server({ port: 3000, host: 'localhost' });\n\nconst init = async () => {\n  await server.register({\n    plugin: require('hapi-saml2'),\n    options: {\n      getSAMLOptions: (request) => ({\n        entryPoint: process.env.SAML_ENTRY_POINT ?? 'https://idp.example.com/ssos',\n        cert: process.env.SAML_CERT ?? '',\n        issuer: 'http://localhost:3000',\n        callbackUrl: 'http://localhost:3000/saml/callback'\n      }),\n      login: async (request, identifier, user) => {\n        // After SAML response validation, identifier is the NameID\n        console.log(`User ${identifier} logging in`);\n        return true;\n      },\n      logout: async (request) => {\n        // Implement logout logic, e.g., clear session\n        console.log('User logged out');\n      },\n      redirectUrlAfterSuccess: '/dashboard',\n      redirectUrlAfterFailure: '/login'\n    }\n  });\n  await server.start();\n  console.log('Server running on %s', server.info.uri);\n};\n\ninit().catch(console.error);","lang":"javascript","description":"Registers the hapi-saml2 plugin with required callbacks and dynamic SAML options from environment variables."},"warnings":[{"fix":"Update login callback to accept (request, identifier, user) and return boolean or object.","message":"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.","severity":"breaking","affected_versions":">=5.0.0"},{"fix":"Move SAML configuration into getSAMLOptions function that returns node-saml options.","message":"Removed samlOptions top-level config. Use getSAMLOptions function instead.","severity":"breaking","affected_versions":">=5.0.0"},{"fix":"Place all SAML-specific options inside the object returned by getSAMLOptions.","message":"Options like 'issuer', 'callbackUrl' directly in options root were removed. Must be inside getSAMLOptions return.","severity":"deprecated","affected_versions":">=5.0.0 <6.0.0"},{"fix":"Upgrade @hapi/hapi to v18+ (tested up to v21).","message":"The plugin requires @hapi/hapi v18 or later. Hapi v17 is not supported (will throw on register).","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Ensure apiPrefix (default '/saml') is not already used by other plugins or routes.","message":"The plugin modifies server routes automatically. If apiPrefix collides with existing routes, registration fails silently with Boom.conflict.","severity":"gotcha","affected_versions":">=5.0.0"}],"env_vars":null,"last_verified":"2026-04-25T00:00:00.000Z","next_check":"2026-07-24T00:00:00.000Z","problems":[{"fix":"Ensure getSAMLOptions always returns an object with required node-saml properties (entryPoint, cert, etc.).","cause":"getSAMLOptions returned undefined or threw inside the function.","error":"Error: SAML instance is not configured"},{"fix":"Add login: async (request, identifier, user) => { return true; } to options.","cause":"Login option is missing or not a function in plugin options.","error":"AssertionError: login must be a function"},{"fix":"Check that Identity Provider sends NameID; use identifier (string) instead of user properties if only NameID is needed.","cause":"user parameter in login callback is undefined if SAML response lacks expected profile attributes.","error":"TypeError: Cannot destructure property 'nameID' of 'user' as it is undefined"}],"ecosystem":"npm","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null}