Last active
December 6, 2019 16:18
-
-
Save petermikitsh/11157140bc727acb7623 to your computer and use it in GitHub Desktop.
SAML implementation for feathers.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 | |
}); | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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'); | |
}); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 🙏
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. 😅