Deploy: The final application!

Here is what the index.js looks like:

const express = require("express");
const passport = require("passport");
const saml = require("passport-saml");
const session = require("express-session");
const bodyParser = require("body-parser");
const fs = require("fs");

const PbK = fs.readFileSync(__dirname + "/certs/cert.pem", "utf8");
const PvK = fs.readFileSync(__dirname + "/certs/key.pem", "utf8");

const JHU_SSO_URL = "https://idp.jh.edu/idp/profile/SAML2/Redirect/SSO";
const SP_NAME = "glacial-plateau-47269";
const BASE_URL = "https://glacial-plateau-47269.herokuapp.com";

// Setup SAML strategy
const samlStrategy = new saml.Strategy(
  {
    // config options here
    entryPoint: JHU_SSO_URL,
    issuer: SP_NAME,
    callbackUrl: `${BASE_URL}/jhu/login/callback`,
    decryptionPvk: PvK,
    privateCert: PvK,
  },
  (profile, done) => {
    return done(null, profile);
  }
);

// Tell passport to use the samlStrategy
passport.use("samlStrategy", samlStrategy);

// Serialize and deserialize user for paqssport
passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(function (user, done) {
  done(null, user);
});

// Initialize express.
const app = express();

// Set up port.
const port = process.env.PORT || 7000;

// Middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(
  session({ secret: "use-any-secret", resave: false, saveUninitialized: true })
);
app.use(passport.initialize({}));
app.use(passport.session({}));

// Set up homepage route
app.get("/", (req, res) => {
  res.send("Test Home Page!");
});

// login route
app.get(
  "/jhu/login",
  (req, res, next) => {
    next();
  },
  passport.authenticate("samlStrategy")
);

// callback route
app.post(
  "/jhu/login/callback",
  (req, res, next) => {
    next();
  },
  passport.authenticate("samlStrategy"),
  (req, res) => {
    // the user data is in req.user
    res.send(`welcome ${req.user.first_name}`);
  }
);

// route to metadata
app.get("/jhu/metadata", (req, res) => {
  res.type("application/xml");
  res.status(200);
  res.send(samlStrategy.generateServiceProviderMetadata(PbK, PbK));
});

// Start the server.
app.listen(port, () => {
  console.log(`Listening on http://localhost:${port}/`);
});

I have deployed the app on Heroku and it is available at https://glacial-plateau-47269.herokuapp.com. Feel free to experiment with it:

  • Go to https://glacial-plateau-47269.herokuapp.com/jhu/login
  • It must redirect you to JHU SSO sign-in page
  • Enter your credential and login
  • It must redirect back to https://glacial-plateau-47269.herokuapp.com/jhu/login/callback
  • You must see a welcome message!

Assertion attributes

The IdP response includes a list of user attributes (assertions). You need to specify, in your correspondence with enterpriseauth@jhmi.edu, what attributes you need so they send it your way.

Here are some of the attributes you can receive (which will be accessible through req.user object in the callback route):

  • Username: req.user.username (your JHED ID)
  • Affiliation: req.user.user_field_affiliation (either STUDENT, FACULTY or STAFF)
  • Job title: req.user.user_field_job_title (e.g. mine comes up as LECTURER)
  • Last name: req.user.last_name
  • First name: req.user.first_name
  • Given name: req.user.given_name
  • Email: req.user.email