-
-
Save petermikitsh/11157140bc727acb7623 to your computer and use it in GitHub Desktop.
/* 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'); | |
}); | |
} |
lol, this JavaScript is 2 years old, but I agree.
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 ๐
- 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
- 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. ๐
This code is begging for async await... otherwise, thanks!