Skip to content

Instantly share code, notes, and snippets.

@petermikitsh
Last active December 6, 2019 16:18
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save petermikitsh/11157140bc727acb7623 to your computer and use it in GitHub Desktop.
Save petermikitsh/11157140bc727acb7623 to your computer and use it in GitHub Desktop.
SAML implementation for feathers.js
/* SAML Authentication Flow
* - Open GET /SSO/SAML2 in an iframe
* - this will redirect to the identity provider ("IdP")
* - The user will insert their credentials in the IdP's website
* - The IdP will redirect to POST /SSO/SAML2
* - The response is validated
* - A user is created (should check if it exists first)
* - Set the JWT cookie
* - Send HTML response to instruct parent window to close the iframe
*/
// this is a slightly abridged version of my implementation (split across various react/redux files)
import authentication from 'feathers-authentication/client'
import feathers from 'feathers/client'
import hooks from 'feathers-hooks'
import io from 'socket.io-client'
import socketio from 'feathers-socketio/client'
var client = feathers()
.configure(hooks())
.configure(socketio(io(window.location.origin)))
.configure(authentication({storage: window.localStorage}));
client
.authenticate()
.then(function (user) {
dispatch({
type: 'SET_USER',
value: user
});
})
'use strict';
var config = require('../config');
var fs = require('fs');
var saml = require('passport-saml');
module.exports = function() {
const app = this;
var strategy = new saml.Strategy({
callbackUrl: 'http://127.0.0.1:3000/SSO/SAML2',
entryPoint: 'https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO',
issuer: 'http://127.0.0.1:3000/shibboleth',
identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
decryptionPvk: fs.readFileSync(config.privateKeyPath, 'utf8'),
privateCert: fs.readFileSync(config.privateKeyPath, 'utf8'),
cert: `{redacted}`,
signatureAlgorithm: 'sha1'
}, function (profile, done) {
return done(null, profile);
});
app.get('/SSO/SAML2', function (req, res) {
strategy._saml.getAuthorizeUrl(req, function (err, result) {
// redirect to the identity provider
res.redirect(err ? '/?loginFailed=true' : result);
});
});
app.post('/SSO/SAML2', function (req, res) {
// decrypt the response with my private key
// validate the response's signature
strategy._saml.validatePostResponse(req.body, function (err, result) {
if (err) {
res.redirect('/?loginFailed=true');
} else {
app
.service('/users')
.create({email: result['urn:oid:1.3.6.1.4.1.5923.1.1.1.6']})
.then(user => {
app
.service('auth/token')
.create(user)
.then(authorization => {
// set cookie
res.cookie('feathers-jwt', authorization.token);
// this is an iframe in a webpage...
// so I instruct the parent to close the iframe
res.end(`
<html>
<body>
<script>
window.parent.closeLogin();
</script>
</body>
</html>`);
})
.catch(error => {
console.log(error);
res.redirect('/?loginFailed=true')
});
})
.catch(error => {
console.log(error);
res.redirect('/?loginFailed=true')
});
}
});
})
app.get('/SSO/SAML2/Metadata', function (req, res) {
var cert = fs.readFileSync(config.publicKeyPath, 'utf8');
res.writeHead(200, {'Content-Type': 'application/xml'});
res.end(strategy._saml.generateServiceProviderMetadata(cert), 'utf-8');
});
}
@FossPrime
Copy link

This code is begging for async await... otherwise, thanks!

@petermikitsh
Copy link
Author

lol, this JavaScript is 2 years old, but I agree.

@cdelaorden
Copy link

Hi @petermikitsh thanks for putting this out. I was reading the issue where you refer to this code and still have some questions, I hope you don't mind I ask you here, it would be terrific if you can give me your input 🙏

  1. How do you use the "cookie" to authenticate using JWT against your Feathers.js backend? AFAIK it only accepts using the JWT Token in Authorization Headers
  2. What's the reasoning behind the iframe stuff you mention?

I'm about to implement SAML integration for a Single Page Application using Feather.js as my API backend, and I guess the IFRAME is the only solution to open an external URL to login but keep your app in the browser, is this the case?

Perhaps you can simply explain a little bit what happens after the iframe is closed, you call again client.authenticate with the token you got in the cookie?

A million thanks in advance, it seems you're the only living man who has done this integration with Feathers apart from me. 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment