Skip to content

Commit 475fa1b

Browse files
committed
Add BU specific config
1 parent 6aeb1fb commit 475fa1b

8 files changed

+620
-76
lines changed

.env.example

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ENTRY_POINT=https://shib-test.bu.edu/idp/profile/SAML2/Redirect/SSO
2+
CALLBACK_URL=https://app.bostonhacks.io/login/callback
3+
ISSUER=https://app.bostonhacks.io/shibboleth
4+
SESSION_SECRET=secret
5+
SHIBBOLETH_CERT='XXXXXXXXXXXXXXXX'
6+
SHIBBOLETH_KEY='XXXXXXXXXXXXXXXX'
7+
SHIBBOLETH_IDP_CERT='XXXXXXXXXXXXXXXX'

.env.sample

-5
This file was deleted.

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
cert/*
33
node_modules/*
44
npm-debug.log
5-
5+
yarn-error.log

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 RIT Student Government
3+
Copyright (c) 2020 BostonHacks
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+13-14
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
1-
# passport-saml-example
1+
# BU SSO Example
22

3-
This node.js web application demonstrates SSO authentication provided by RIT's Shibboleth Server (https://shibboleth.main.ad.rit.edu/), using the `passport-saml` package.
3+
This Node.js web application demonstrates SSO authentication provided by BU's Shibboleth Server using the `passport-saml` package.
44

55
Config
66
======
7-
8-
This app requires 3 files to be placed in a folder named `cert` located in the project's root directory. These files include (1) the certificate of the Identity Provider (IdP). In this case, RIT's Shibboleth Server is the IdP. As a Service Provider (SP), you need to generate your own (2) certificate and (3) private key. These files are named as follows:
7+
In order to use Shibboleth authentication, your application requires a certificate/private key pair, as well as BU's Shibboleth Identity Provider (IdP) Certificate. The files should be named as such:
98

109
- `cert.pem`: SP's certificate (Generated by you)
11-
- `cert_idp.pem`: IdP's certificate (RIT's is contained in https://shibboleth.main.ad.rit.edu/rit-metadata.xml)
1210
- `key.pem`: SP's private key (Generated by you)
11+
- `cert_idp.pem`: IdP's certificate (BU's is contained in https://shib-test.bu.edu/idp/shibboleth)
12+
13+
Alternatively, instead of storing the certificates in files, they can be stored as environment variables. Simply collapse the contents of each file into one line, inserting `\n` for line breaks, and then store them in your `.env` file. This can be useful when running your application on a service like Heroku and you want to minimize the number/size of files uploaded, or you are not allowed to upload extra files, etc.
1314

1415
Creating Private Key and Certificates
1516
=====================================
16-
17-
Generate the SP files with the following command:
17+
Generate your certificate/private key files with the following command:
1818
- `openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 900`
1919

20-
The IdP Certificate is contained within the `ds:X509Certificate` tag.
20+
The [IdP Certificate](https://shib-test.bu.edu/idp/shibboleth) is contained within the `ds:X509Certificate` tag.
2121
- Copy the tag's contents into a file named `cert_idp.pem`.
2222

23-
Next, copy `.env.sample` to `.env` and edit appropriately. Running this app locally will likely not work since the IdP can't redirect to `localhost`.
23+
Next, copy `.env.example` to `.env` and edit appropriately. Running this app locally will likely not work since the IdP can't redirect to `localhost`.
2424

2525
Registering the Service Provider
2626
================================
27-
28-
Contact ITS to register your Service Provider. During this step, the IdP Administrator downloads the metadata from the `/Shibboleth.sso/Metadata` endpoint and loads it into the IdP.
27+
Fill out BU's [Service Provider Checklist](https://www.bu.edu/tech/services/security/iam/authentication/shibboleth/service-provider-checklist/) form to register your application (for the "Service Provider Metadata" textbox, go to your app's `/shibboleth/metadata` route and copy the entire XML content into the textbox). After a few days, it will be processed and your authentication setup will be live!
2928

3029
Usage
3130
=====
32-
3331
```
34-
npm install
35-
node app.js
32+
yarn install
33+
yarn start
3634
```
3735

36+
Shoutout to Dharmesh Tarapore's [BU Shibboleth guide](https://cs-people.bu.edu/dharmesh/teaching/shibboleth/) for Apache, it was very useful in figuring out the BU specific configuration for `passport-saml`. Another shoutout to the [RIT Student Goverment](https://github.com/ritstudentgovernment), who's repo we forked.

app.js

+54-45
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
var http = require('http');
2-
var fs = require('fs');
3-
var express = require("express");
4-
var dotenv = require('dotenv');
5-
var bodyParser = require('body-parser');
6-
var cookieParser = require('cookie-parser');
7-
var session = require('express-session');
8-
var passport = require('passport');
9-
var saml = require('passport-saml');
10-
11-
dotenv.load();
1+
const fs = require('fs');
2+
const express = require("express");
3+
const dotenv = require('dotenv');
4+
const bodyParser = require('body-parser');
5+
const cookieParser = require('cookie-parser');
6+
const session = require('express-session');
7+
const passport = require('passport');
8+
const SamlStrategy = require('passport-saml').Strategy;
9+
10+
dotenv.config();
1211

1312
passport.serializeUser(function(user, done) {
1413
done(null, user);
@@ -18,37 +17,51 @@ passport.deserializeUser(function(user, done) {
1817
done(null, user);
1918
});
2019

21-
var samlStrategy = new saml.Strategy({
20+
const SamlOptions = {
2221
// URL that goes from the Identity Provider -> Service Provider
2322
callbackUrl: process.env.CALLBACK_URL,
2423
// URL that goes from the Service Provider -> Identity Provider
2524
entryPoint: process.env.ENTRY_POINT,
2625
// Usually specified as `/shibboleth` from site root
2726
issuer: process.env.ISSUER,
2827
identifierFormat: null,
29-
// Service Provider private key
30-
decryptionPvk: fs.readFileSync(__dirname + '/cert/key.pem', 'utf8'),
31-
// Service Provider Certificate
32-
privateCert: fs.readFileSync(__dirname + '/cert/key.pem', 'utf8'),
33-
// Identity Provider's public key
34-
cert: fs.readFileSync(__dirname + '/cert/idp_cert.pem', 'utf8'),
3528
validateInResponseTo: false,
3629
disableRequestedAuthnContext: true
37-
}, function(profile, done) {
38-
return done(null, profile);
39-
});
30+
}
31+
32+
// Service Provider private key
33+
if (process.env.SHIBBOLETH_KEY) {
34+
SamlOptions.decryptionPvk = JSON.parse(`"${process.env.SHIBBOLETH_KEY}"`);
35+
SamlOptions.privateCert = JSON.parse(`"${process.env.SHIBBOLETH_KEY}"`);
36+
} else {
37+
SamlOptions.decryptionPvk = fs.readFileSync(__dirname + '/cert/key.pem', 'utf8');
38+
SamlOptions.privateCert = fs.readFileSync(__dirname + '/cert/key.pem', 'utf8');
39+
}
40+
41+
// Identity Provider's public key
42+
if (process.env.SHIBBOLETH_IDP_CERT) {
43+
SamlOptions.cert = JSON.parse(`"${process.env.SHIBBOLETH_IDP_CERT}"`);
44+
} else {
45+
SamlOptions.cert = fs.readFileSync(__dirname + '/cert/cert_idp.pem', 'utf8');
46+
}
4047

48+
const samlStrategy = new SamlStrategy(SamlOptions, (profile, done) => done(null, profile));
4149
passport.use(samlStrategy);
4250

43-
var app = express();
51+
const app = express();
4452

4553
app.use(cookieParser());
46-
app.use(bodyParser());
47-
app.use(session({secret: process.env.SESSION_SECRET}));
54+
app.use(bodyParser.json());
55+
app.use(bodyParser.urlencoded({ extended: true }));
56+
app.use(session({
57+
secret: process.env.SESSION_SECRET,
58+
resave: true,
59+
saveUninitialized: true
60+
}));
4861
app.use(passport.initialize());
4962
app.use(passport.session());
5063

51-
function ensureAuthenticated(req, res, next) {
64+
const ensureAuthenticated = (req, res, next) => {
5265
if (req.isAuthenticated())
5366
return next();
5467
else
@@ -57,45 +70,41 @@ function ensureAuthenticated(req, res, next) {
5770

5871
app.get('/',
5972
ensureAuthenticated,
60-
function(req, res) {
61-
res.send('Authenticated');
62-
}
73+
(req, res) => res.send('Authenticated')
6374
);
6475

6576
app.get('/login',
6677
passport.authenticate('saml', { failureRedirect: '/login/fail' }),
67-
function (req, res) {
68-
res.redirect('/');
69-
}
78+
(req, res) => res.redirect('/')
7079
);
7180

7281
app.post('/login/callback',
7382
passport.authenticate('saml', { failureRedirect: '/login/fail' }),
74-
function(req, res) {
75-
res.redirect('/');
76-
}
83+
(req, res) => res.redirect('/')
7784
);
7885

7986
app.get('/login/fail',
80-
function(req, res) {
81-
res.status(401).send('Login failed');
82-
}
87+
(req, res) => res.status(401).send('Login failed')
8388
);
8489

85-
app.get('/Shibboleth.sso/Metadata',
86-
function(req, res) {
90+
app.get('/shibboleth/metadata',
91+
(req, res) => {
8792
res.type('application/xml');
88-
res.status(200).send(samlStrategy.generateServiceProviderMetadata(fs.readFileSync(__dirname + '/cert/cert.pem', 'utf8')));
93+
let cert = null;
94+
if (process.env.SHIBBOLETH_CERT) {
95+
cert = JSON.parse(`"${process.env.SHIBBOLETH_CERT}"`);
96+
} else {
97+
cert = fs.readFileSync(__dirname + '/cert/cert.pem', 'utf8');
98+
}
99+
res.status(200).send(samlStrategy.generateServiceProviderMetadata(cert, cert));
89100
}
90101
);
91102

92103
//general error handler
93104
app.use(function(err, req, res, next) {
94-
console.log("Fatal error: " + JSON.stringify(err));
105+
console.error("Fatal error: " + JSON.stringify(err));
95106
next(err);
96107
});
97108

98-
var server = app.listen(4006, function () {
99-
console.log('Listening on port %d', server.address().port)
100-
});
101-
109+
const serverPort = process.env.PORT || 3030;
110+
const server = app.listen(serverPort, () => console.log(`Listening on port ${serverPort}`));

package.json

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
{
2-
"name": "passport-saml-rit-example",
3-
"version": "0.0.0",
4-
"description": "An example web app that connects to RIT's SSO Shibboleth Identity Provider.",
2+
"name": "passport-saml-bu-example",
3+
"version": "1.0.0",
4+
"description": "An example web app that connects to BU's SSO Shibboleth Identity Provider.",
55
"dependencies": {
6-
"body-parser": "1.11.0",
7-
"cookie-parser": "1.3.3",
8-
"dotenv": "0.5.1",
9-
"express": "4.11.2",
10-
"express-session": "1.10.2",
11-
"passport": "0.2.1",
12-
"passport-saml": "0.9.0"
6+
"cookie-parser": "^1.4.4",
7+
"dotenv": "^8.2.0",
8+
"express": "^4.17.1",
9+
"express-session": "^1.17.0",
10+
"passport": "^0.4.1",
11+
"passport-saml": "^1.3.3"
12+
},
13+
"scripts": {
14+
"start": "node app.js"
1315
}
1416
}

0 commit comments

Comments
 (0)