A Passport.js strategy for integrating UBC's Shibboleth SAML 2.0 Identity Provider with Node.js applications. An extension of the passport-saml library, with thanks to University of Washington for publishing theirs from which we could work: https://www.passportjs.org/packages/passport-uwshib/
- SAML 2.0 Support: Full SAML 2.0 authentication with UBC's Shibboleth IdP
- Multi-Environment: Automatic configuration for LOCAL (SimpleSAMLphp), STAGING, and PRODUCTION environments
- Local Development: Test with docker-simple-saml before deploying to staging/production
- Flexible Attributes: Per-application attribute selection with OID-to-friendly-name mapping
- Secure: Supports SAML request signing and response validation
- Single Logout (SLO): Optional support for single logout at the IdP
- Certificate Management: Automatic fetching of IdP certificates from metadata
- Environment-Based Configuration: Support for both environment variables and runtime configuration
npm install passport-ubcshib passportconst passport = require('passport');
const { Strategy } = require('passport-ubcshib');
const fs = require('fs');
passport.use(
new Strategy(
{
// Service Provider Identity (usually your app's URL)
issuer: 'https://myapp.example.com/shibboleth',
// Callback URL after authentication
callbackUrl: 'https://myapp.example.com/auth/ubcshib/callback',
// Path to your application's private key for signing SAML requests
privateKeyPath: process.env.SAML_PRIVATE_KEY_PATH,
// Specify which attributes your app needs
// See ATTRIBUTES.md for full list of available attributes
attributeConfig: ['ubcEduCwlPuid', 'mail', 'eduPersonAffiliation'],
// Optional: Enable single logout
enableSLO: true,
},
// Verify callback: called with user profile after successful authentication
(profile, done) => {
// profile.nameID - SAML nameID
// profile.attributes - Mapped attributes based on attributeConfig
User.findOrCreate(
{ ubcId: profile.attributes.ubcEduCwlPuid },
(err, user) => {
return done(err, user);
}
);
}
)
);
// Serialize and deserialize user for session management
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});const express = require('express');
const passport = require('passport');
const { ensureAuthenticated, logout } = require('passport-ubcshib');
const app = express();
// Login route - redirects to UBC IdP
app.get('/auth/ubcshib', passport.authenticate('ubcshib'));
// Callback route - called by UBC IdP after authentication
app.post(
'/auth/ubcshib/callback',
passport.authenticate('ubcshib', { failureRedirect: '/login' }),
(req, res) => {
// Successful authentication
res.redirect('/dashboard');
}
);
// Protected route
app.get('/dashboard', ensureAuthenticated(), (req, res) => {
res.send(`Welcome, ${req.user.name}`);
});
// Logout
app.get('/logout', logout('/'));Create a .env file:
# SAML Configuration
SAML_ENVIRONMENT=STAGING # or PRODUCTION
SAML_PRIVATE_KEY_PATH=/path/to/your/private.key
SAML_ISSUER=https://myapp.example.com/shibboleth
SAML_CALLBACK_URL=https://myapp.example.com/auth/ubcshib/callback
# Attributes your app needs
# (optional - can also configure in code)| Option | Type | Description |
|---|---|---|
issuer |
string | Your Service Provider's entity ID (usually your app's base URL + /shibboleth) |
callbackUrl |
string | URL where the IdP redirects after authentication (must match registered URL with UBC IAM) |
| Option | Type | Default | Description |
|---|---|---|---|
privateKeyPath |
string | null | Path to your app's private key for signing SAML requests |
attributeConfig |
Array | [] | List of attribute names to request from IdP (see ATTRIBUTES.md) |
entryPoint |
string | Staging or Production SSO endpoint | Identity Provider's SSO endpoint |
logoutUrl |
string | Staging or Production logout endpoint | Identity Provider's logout endpoint |
enableSLO |
boolean | true | Enable single logout (SLO) on user logout |
validateInResponseTo |
boolean | true | Validate response InResponseTo against request ID |
acceptedClockSkewMs |
number | 0 | Allowed clock skew in milliseconds between servers |
signatureAlgorithm |
string | 'sha256' | SAML signature algorithm (sha256, sha512, etc.) |
The following environment variables can be used to configure the strategy:
# Environment selection
SAML_ENVIRONMENT=STAGING # STAGING or PRODUCTION
# For custom IdP configuration
SAML_ENTRY_POINT=https://authentication.ubc.ca/idp/profile/SAML2/Redirect/SSO
SAML_LOGOUT_URL=https://authentication.ubc.ca/idp/profile/Logout
SAML_METADATA_URL=https://authentication.ubc.ca/idp/shibboleth
# Private key location
SAML_PRIVATE_KEY_PATH=/etc/app/saml/private.key
# Service Provider config
SAML_ISSUER=https://myapp.example.com/shibboleth
SAML_CALLBACK_URL=https://myapp.example.com/auth/ubcshib/callback
# Enable/disable features
ENABLE_SLO=true- Entry Point:
https://authentication.stg.id.ubc.ca/idp/profile/SAML2/Redirect/SSO - Logout URL:
https://authentication.stg.id.ubc.ca/idp/profile/Logout - Metadata:
https://authentication.stg.id.ubc.ca/idp/shibboleth
- Entry Point:
https://authentication.ubc.ca/idp/profile/SAML2/Redirect/SSO - Logout URL:
https://authentication.ubc.ca/idp/profile/Logout - Metadata:
https://authentication.ubc.ca/idp/shibboleth
Set the environment via:
SAML_ENVIRONMENT=PRODUCTION node app.jsThe strategy automatically maps SAML OID attribute names to friendly names. See ATTRIBUTES.md for the complete reference.
Common attributes:
ubcEduCwlPuid- UBC Computing User IDmail- Email addresseduPersonAffiliation- Affiliation (student, faculty, staff, etc.)
Need to add support for a new attribute? See ADDING_ATTRIBUTES.md for a step-by-step guide on how to add new OID mappings to the library.
After successful authentication, mapped attributes are available as:
req.user.attributes = {
ubcEduCwlPuid: '12345678',
mail: '[email protected]',
eduPersonAffiliation: ['student', 'member'],
};Middleware to protect routes. Redirects unauthenticated users to login.
const { ensureAuthenticated } = require('@ubcshib/passport-ubcshib');
app.get('/protected', ensureAuthenticated(), (req, res) => {
res.send('Only authenticated users see this');
});
// Custom login URL
app.get(
'/protected',
ensureAuthenticated({ loginUrl: '/auth/ubcshib' }),
(req, res) => {
res.send('Only authenticated users see this');
}
);Middleware to handle logout. Clears session and optionally redirects to IdP logout if SLO is enabled.
const { logout } = require('@ubcshib/passport-ubcshib');
app.get('/logout', logout('/goodbye'));Middleware to conditionally protect routes based on a function.
const { conditionalAuth } = require('@ubcshib/passport-ubcshib');
app.get(
'/maybe-protected',
conditionalAuth((req) => req.query.admin === 'true'),
(req, res) => {
res.send('Protected if admin query param is true');
}
);After successful authentication, the user profile will have:
profile = {
nameID: '[email protected]', // SAML nameID (usually email)
nameIDFormat: '...', // SAML format identifier
attributes: {
// Mapped attributes (from attributeConfig)
ubcEduCwlPuid: '12345678',
mail: '[email protected]',
eduPersonAffiliation: ['student'],
},
};The strategy automatically fetches the IdP's public certificate from the metadata endpoint. No configuration is needed for certificate handling in most cases.
If you need to provide a certificate manually:
new Strategy(
{
// ... other options
cert: fs.readFileSync('/path/to/idp-cert.pem', 'utf8'),
},
verifyCallback
);-
Request metadata from UBC IAM:
- Provide them with your Service Provider's metadata
- They will configure your app in their staging environment
- They will provide you with Entity ID confirmation
-
Test in Staging:
SAML_ENVIRONMENT=STAGING SAML_PRIVATE_KEY_PATH=/path/to/key node app.js
-
Validate attributes:
- Visit
https://authentication.stg.id.ubc.ca/idp/shibbolethto see available attributes - Add to
attributeConfigin your code
- Visit
-
Work with UBC IAM to:
- Register your production URLs (issuer, callbackUrl)
- Confirm entity ID and metadata
- Verify attributes are configured correctly
-
Deploy with production settings:
SAML_ENVIRONMENT=PRODUCTION node app.js
- Ensure
SAML_PRIVATE_KEY_PATHpoints to an existing file - Check file permissions (app must be able to read it)
- Verify the file contains a valid PEM-formatted private key
- Check that the IdP metadata URL is accessible
- Verify you're using the correct environment
- Try accessing the metadata URL in a browser
- Verify your private key matches what UBC IAM has on file
- Check that your Service Provider's entityID (issuer) matches what's registered with UBC
- Ensure
callbackUrlexactly matches what's registered with UBC IAM - Check server clocks are synchronized (NTP)
- Verify attributes are listed in your
attributeConfig - Check that UBC IAM has authorized release of those attributes to your app
- Use the
attributeConfigto only request attributes your app truly needs
This package includes comprehensive documentation for different use cases:
- LOCAL_SETUP.md - Quick start guide for local development with SimpleSAMLphp using docker-simple-saml
- INTEGRATION-GUIDE.md - Complete integration guide for staging and production deployment
- ATTRIBUTES.md - Reference guide for all available SAML attributes
- ADDING_ATTRIBUTES.md - Guide for adding new attributes to the library
- GETTING_STARTED.md - High-level overview and key concepts
- TESTING.md - Testing strategies and debugging techniques
- DEVELOPMENT.md - For library maintainers and contributors
For issues, questions, or contributions, please contact the UBC IAM team.
GPL v3