Modern SAML 2.0 authentication middleware for Express.js with TypeScript support.
- π SAML 2.0 Compliant - Full implementation of SAML 2.0 specification
- π‘οΈ Secure by Default - Signature verification, certificate validation, timing checks
- π TypeScript First - Complete type definitions and IntelliSense support
- π― Simple API - Easy-to-use middleware pattern for Express
- π Single Logout (SLO) - Support for identity provider initiated logout
- π Metadata Generation - Automatic SP metadata for IdP configuration
- β‘ Modern Stack - Uses latest JavaScript/TypeScript standards
- π§ͺ Well Tested - Comprehensive test coverage
- π Excellent Documentation - Clear examples and guides
npm install @bozzltron/express-samlimport express from 'express';
import session from 'express-session';
import { createSAMLMiddleware } from '@bozzltron/express-saml';
const app = express();
// Required: Session middleware
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: { secure: true }
}));
// Create SAML middleware
const saml = createSAMLMiddleware({
// Your application (Service Provider)
issuer: 'my-app',
callbackUrl: 'https://myapp.com/auth/saml/callback',
// Identity Provider
entryPoint: 'https://idp.com/sso/saml',
idpCert: process.env.SAML_IDP_CERT,
// Where to redirect after authentication
successRedirect: '/dashboard',
failureRedirect: '/login',
});
// Setup SAML routes
app.get('/auth/saml/login', saml.authenticate());
app.post('/auth/saml/callback', saml.callback());
app.get('/auth/saml/logout', saml.logout());
app.get('/auth/saml/metadata', saml.metadata());
// Protect routes
app.get('/dashboard', saml.requireAuth(), (req, res) => {
res.json({ user: req.samlUser });
});
app.listen(3000);Create a .env file:
# Service Provider
SAML_ISSUER=my-application
SAML_CALLBACK_URL=https://myapp.com/auth/saml/callback
# Identity Provider
SAML_ENTRY_POINT=https://idp.com/sso/saml
SAML_IDP_CERT="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
# Optional: Sign requests
SAML_SIGN_REQUEST=false
SAML_PRIVATE_KEY="..."interface SAMLMiddlewareOptions {
// Required: Service Provider Configuration
issuer: string; // Your app's entity ID
callbackUrl: string; // Where SAML responses are posted
entryPoint: string; // IdP SSO URL
idpCert: string | string[]; // IdP certificate(s)
// Optional: Security
signRequest?: boolean; // Sign authentication requests
privateKey?: string; // SP private key (if signing)
cert?: string; // SP certificate
validateSignature?: boolean; // Verify response signatures (default: true)
wantAssertionsSigned?: boolean; // Require signed assertions (default: true)
clockSkew?: number; // Time tolerance in ms (default: 0)
// Optional: Behavior
successRedirect?: string; // Where to go after login (default: '/')
failureRedirect?: string; // Where to go on error (default: '/login')
sessionProperty?: string; // Session key for user (default: 'samlUser')
forceAuthn?: boolean; // Force re-authentication
passive?: boolean; // Don't prompt for credentials
// Optional: Callbacks
onAuth?: (profile, req, res) => Promise<boolean | void>;
onError?: (error, req, res) => void;
// Optional: Logout
logoutUrl?: string; // IdP logout URL
}Initiates SAML authentication by redirecting to the Identity Provider.
app.get('/login', saml.authenticate());Handles SAML responses from the Identity Provider (Assertion Consumer Service).
app.post('/auth/callback', saml.callback());Middleware to protect routes - requires active SAML session.
app.get('/protected', saml.requireAuth(), (req, res) => {
// req.samlUser is available here
});Clears session and optionally initiates Single Logout with IdP.
app.get('/logout', saml.logout());Provides SP metadata XML for IdP configuration.
app.get('/metadata', saml.metadata());After successful authentication, req.samlUser contains:
interface SAMLProfile {
nameID: string; // User identifier
email?: string; // Email address
firstName?: string; // First name
lastName?: string; // Last name
sessionIndex?: string; // Session index for SLO
attributes: Record<string, string | string[]>; // All IdP attributes
}const saml = createSAMLMiddleware({
// ... other options
onAuth: async (profile, req, res) => {
// Custom logic: create/update user in database
const user = await db.users.findOrCreate({
email: profile.email,
firstName: profile.firstName,
lastName: profile.lastName,
});
// Store custom session data
req.session.userId = user.id;
req.session.roles = profile.attributes.roles;
// Return true to continue with default session handling
return true;
},
});const saml = createSAMLMiddleware({
// ... other options
onError: (error, req, res) => {
console.error('SAML Error:', error);
// Log to monitoring service
logger.error('SAML authentication failed', { error, user: req.ip });
// Custom error page
res.status(401).render('auth-error', {
message: 'Authentication failed. Please try again.'
});
},
});For enhanced security, sign authentication requests:
const saml = createSAMLMiddleware({
issuer: 'my-app',
callbackUrl: 'https://myapp.com/auth/callback',
entryPoint: 'https://idp.com/sso',
idpCert: process.env.IDP_CERT,
// Enable request signing
signRequest: true,
privateKey: fs.readFileSync('./certs/private-key.pem', 'utf8'),
cert: fs.readFileSync('./certs/certificate.pem', 'utf8'),
});Support certificate rotation by providing multiple certificates:
const saml = createSAMLMiddleware({
// ... other options
idpCert: [
process.env.IDP_CERT_CURRENT,
process.env.IDP_CERT_OLD, // Still valid during rotation
],
});- Always use HTTPS in production
- Validate signatures (enabled by default)
- Use secure session configuration:
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: true, // HTTPS only httpOnly: true, // Prevent XSS sameSite: 'lax', // CSRF protection maxAge: 86400000 // 24 hours } }));
- Rotate certificates regularly
- Set clock skew appropriately (0-5 minutes)
- Use environment variables for sensitive data
- Monitor authentication failures
- Keep dependencies updated
The package includes comprehensive tests that prove the middleware works with Express:
- β Unit Tests - Core SAML logic (request generation, response validation, crypto utilities)
- β Integration Tests - Full Express middleware integration with real Express apps
- β End-to-End Tests - Complete authentication flows from login to protected routes
The integration tests verify:
- β Middleware integrates correctly with Express routing
- β Sessions are created and persisted correctly
- β
Route protection (
requireAuth()) works as expected - β SAML requests are generated and redirects work
- β SAML responses are parsed and user profiles extracted
- β Logout clears sessions properly
- β
Custom callbacks (
onAuth,onError) are invoked - β Redirect behavior (successRedirect, returnTo) works correctly
- β Error handling works gracefully
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Watch mode
npm run test:watch
# Run specific test file
npm test saml.integration.test.tsSee TESTING.md for detailed testing documentation.
See the src/example/ directory for complete working examples:
- Basic Authentication - Simple SAML integration
- Database Integration - User creation and lookup
- Role-Based Access - Using SAML attributes for authorization
- Multi-Tenant - Supporting multiple IdPs
- User visits protected route
- Redirect to
/auth/saml/login(if not authenticated) - Middleware generates SAML AuthnRequest
- User redirected to IdP for authentication
- User authenticates at IdP
- IdP posts SAML Response to callback URL
- Middleware validates response (signature, timing, audience)
- User session created with profile data
- User redirected to original route
- Entity ID (Issuer): Your
issuervalue - ACS URL: Your
callbackUrl(POST binding) - SP Metadata: From
/auth/saml/metadataendpoint - Name ID Format: Email address (default)
- Attributes to send:
- First Name
- Last Name
- Any custom attributes
- Okta: SAML Setup Guide
- Auth0: SAML Configuration
- Azure AD: Enterprise SSO
- OneLogin: SAML Apps
Ensure certificates are properly formatted:
// Remove headers and whitespace
const cert = process.env.IDP_CERT
.replace(/-----BEGIN CERTIFICATE-----/, '')
.replace(/-----END CERTIFICATE-----/, '')
.replace(/\s/g, '');If you get timing errors, add clock skew tolerance:
const saml = createSAMLMiddleware({
// ... other options
clockSkew: 300000, // 5 minutes tolerance
});- Verify certificate is correct
- Check certificate hasn't expired
- Ensure IdP is signing responses
- Try disabling validation temporarily for debugging (NOT in production)
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT Β© Michael Bosworth
Built with modern web standards and inspired by:
- π Report Issues
- π§ Email: [email protected]
- π¬ Discussions
Made with β€οΈ by Michael Bosworth