-
-
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!