Skip to content

How to use with ADFS

Chris Barth edited this page May 29, 2023 · 2 revisions

Active Directory Federation Services

This document attempts to describe a complete solution for integrating with AD FS.

Creating a self-signed certificate

Create a folder to contain your keys and certificates.

Download mellon_create_metadata.sh

Create a new key, certificate and relying party XML as follows:

./mellon_create_metadata.sh acme_tools_com https://acme_tools.com/adfs/postResponse

Retrieve ADFS Certificate

Use retrieve_adfs_certificate.sh to get your AD FS server's signing certificate:

retrieve_adfs_certificate.sh

#!/bin/sh

# Author: Tim Brody <[email protected]>
# Date: 2015-02-11
#
# Retrieve the signing certificate from an ADFS instance in PEM format.

ADFS_SERVER=$1

if [ "$#" -ne "1" ]; then
  echo "Usage: $0 <adfs server URL>"
  exit 1
fi

URL=$ADFS_SERVER/FederationMetadata/2007-06/FederationMetadata.xml
TEMPFILE=$(mktemp)

if [[ $(command -v wget) ]]; then
    wget --no-check-certificate -q -O $TEMPFILE $URL
elif [[ $(command -v curl) ]]; then
        curl -sk $URL -o $TEMPFILE
    else
        echo "Neither curl or wget was found"
        exit 127
fi

if [ $? -ne 0 ]; then
  echo "Error requesting $URL"
  exit 1
fi

echo "-----BEGIN CERTIFICATE-----"
(xmllint --shell $TEMPFILE | grep -v '^/ >' | grep -v '^ ----' | fold -w 64) << EndOfScript
setns a=urn:oasis:names:tc:SAML:2.0:metadata
setns b=http://www.w3.org/2000/09/xmldsig#
cat /a:EntityDescriptor/b:Signature/b:KeyInfo/b:X509Data/b:X509Certificate/text()
EndOfScript
echo "-----END CERTIFICATE-----"

unlink $TEMPFILE
./retrieve_adfs_certificate.sh https://adfs.acme_tools.com/ > adfs.acme_tools.com.crt

Create Relying Party

Copy the acme_tools_com.xml to your AD FS server. Use the AD FS management relying party wizard to import this XML, creating a relying party. Create claim rules to pass authentication information to your application.

This example assumes you will pass in the UPN.

Set AD FS Relying Party to sign the message and the assertion

Set-AdfsRelyingPartyTrust -TargetIdentifier "acme_tools_com" -SamlResponseSignature "MessageAndAssertion"

Create a Passport framework

Create a separate file for passport configuration (assumed to be config/passport.js).

var fs = require("fs"),
  passport = require("passport"),
  SamlStrategy = require("passport-saml").Strategy;
passport.serializeUser(function (user, done) {
  done(null, user);
});
passport.deserializeUser(function (user, done) {
  done(null, user);
});

passport.use(
  new SamlStrategy(
    {
      entryPoint: "https://adfs.acme_tools.com/adfs/ls/",
      issuer: "acme_tools_com",
      callbackUrl: "https://acme_tools.com/adfs/postResponse",
      privateKey: fs.readFileSync("/path/to/acme_tools_com.key", "utf-8"),
      cert: fs.readFileSync("/path/to/adfs.acme_tools.com.crt", "utf-8"),
      // other authn contexts are available e.g. windows single sign-on
      // see: https://learn.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.authenticationmethods?view=netframework-4.8#fields
      authnContext: [
        "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password",
      ],
      identifierFormat: null,
      // this is configured under the Advanced tab in AD FS relying party
      signatureAlgorithm: "sha256",
      racComparison: "exact", // default to exact RequestedAuthnContext Comparison Type
      // From the metadata document
      audience: "https://adfs.acme_tools.com/FederationMetadata/2007-06/FederationMetadata.xml",
    },
    function (profile, done) {
      return done(null, {
        upn: profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"],
        // e.g. if you added a Group claim
        group: profile["http://schemas.xmlsoap.org/claims/Group"],
      });
    }
  )
);

module.exports = passport;

Express Application

  var passport = require('passport');
  var express = require('express');
  ... etc.

  var app = express();
  require('./config/passport.js');

  ... add the usual express pro-forma

  app.use(passport.initialize());
  app.use(passport.session());

  app.get('/login',
    passport.authenticate('saml', { failureRedirect: '/', failureFlash: true }),
    function(req, res) {
      res.redirect('https://acme_tools.com');
    }
  );
  app.post('/adfs/postResponse',
    passport.authenticate('saml', { failureRedirect: '/', failureFlash: true }),
    function(req, res) {
      res.redirect('https://acme_tools.com');
    }
  );
  app.get('/secure', validUser, routes.secure);

  function validUser(req, res, next) {
    if (!req.user) {
      res.redirect('https://acme_tools.com/login');
    }
    next();
  }

  var server = http.createServer(app);

Troubleshooting

ADFS 2016

If you are setting up an ADFS 2016 server, you might run into the following issue with the previous settings:

An error occurred during an attempt to read the federation metadata. Verify that the specified URL or host name is a valid federation metadata endpoint.

Verify your proxy server setting. For more information about how to verify you proxy sever setting, see the AD FS Troubleshooting Guide http://go.microsoft.com/fwlink/?LinkId=182180).
Error message: EntityDescriptor 'acme_tools_com'. ID0014: The value 'NameIDFormat' must be an absolute URI.

NameIDFormat Error Popup

If you remove the identifierFormat, it works as expected.