Skip to content

Commit 212bd68

Browse files
committed
Support dynamic SAML configuration lookup
1 parent f8140aa commit 212bd68

File tree

4 files changed

+226
-6
lines changed

4 files changed

+226
-6
lines changed

README.md

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ Passport-SAML has been tested to work with Onelogin, Okta, Shibboleth, [SimpleSA
1616

1717
## Usage
1818

19-
### Configure strategy
19+
The examples utilize the [Feide OpenIdp identity provider](https://openidp.feide.no/). You need an account there to log in with this. You also need to [register your site](https://openidp.feide.no/simplesaml/module.php/metaedit/index.php) as a service provider.
2020

21-
This example utilizes the [Feide OpenIdp identity provider](https://openidp.feide.no/). You need an account there to log in with this. You also need to [register your site](https://openidp.feide.no/simplesaml/module.php/metaedit/index.php) as a service provider.
21+
### Configure strategy
2222

2323
The SAML identity provider will redirect you to the URL provided by the `path` configuration.
2424

@@ -43,6 +43,36 @@ passport.use(new SamlStrategy(
4343
);
4444
```
4545

46+
### Configure strategy for multiple providers
47+
48+
You can pass a `getSamlOptions` parameter to `MultiSamlStrategy` which will be called before the SAML flows. Passport-SAML will pass in the request object so you can decide which configuation is appropriate.
49+
50+
```javascript
51+
var MultiSamlStrategy = require('passport-saml').MultiSamlStrategy;
52+
[...]
53+
54+
passport.use(new MultiSamlStrategy(
55+
{
56+
getSamlOptions: function(request, done) {
57+
findProvider(request, function(err, provider) {
58+
if (err) {
59+
return done(err);
60+
}
61+
return done(null, provider.configuration);
62+
});
63+
}
64+
},
65+
function(profile, done) {
66+
findByEmail(profile.email, function(err, user) {
67+
if (err) {
68+
return done(err);
69+
}
70+
return done(null, user);
71+
});
72+
})
73+
);
74+
```
75+
4676
#### Config parameter details:
4777

4878
* **Core**
@@ -237,10 +267,6 @@ See [Releases](https://github.com/bergie/passport-saml/releases) to find the cha
237267
238268
## FAQ
239269
240-
### What if I have multiple SAML providers that my users may be connecting to?
241-
242-
A single instance of passport-saml will only authenticate users against a single identity provider. If you have a use case where different logins need to be routed to different identity providers, you can create multiple instances of passport-saml, and either dispatch to them with your own routing code, or use a library like https://www.npmjs.org/package/passports.
243-
244270
### Is there an example I can look at?
245271
246272
Gerard Braad has provided an example app at https://github.com/gbraad/passport-saml-example/

lib/passport-saml/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
exports.Strategy = require('./strategy');
2+
exports.MultiSamlStrategy = require('./multiSamlStrategy');
23
exports.SAML = require('./saml').SAML;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
var util = require('util');
2+
var saml = require('./saml');
3+
var SamlStrategy = require('./strategy');
4+
5+
function MultiSamlStrategy (options, verify) {
6+
if (!options || typeof options.getSamlOptions != 'function') {
7+
throw new Error('Please provide a getSamlOptions function');
8+
}
9+
10+
SamlStrategy.call(this, options, verify);
11+
this._getSamlOptions = options.getSamlOptions;
12+
}
13+
14+
util.inherits(MultiSamlStrategy, SamlStrategy);
15+
16+
MultiSamlStrategy.prototype.authenticate = function (req, options) {
17+
var self = this;
18+
19+
this._getSamlOptions(req, function (err, samlOptions) {
20+
if (err) {
21+
return self.error(err);
22+
}
23+
24+
self._saml = new saml.SAML(samlOptions);
25+
self.constructor.super_.prototype.authenticate.call(self, req, options);
26+
});
27+
};
28+
29+
MultiSamlStrategy.prototype.logout = function (req, options) {
30+
var self = this;
31+
32+
this._getSamlOptions(req, function (err, samlOptions) {
33+
if (err) {
34+
return self.error(err);
35+
}
36+
37+
self._saml = new saml.SAML(samlOptions);
38+
self.constructor.super_.prototype.logout.call(self, req, options);
39+
});
40+
};
41+
42+
module.exports = MultiSamlStrategy;

test/multiSamlStrategy.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use strict';
2+
3+
var sinon = require('sinon');
4+
var should = require( 'should' );
5+
var SamlStrategy = require( '../lib/passport-saml/index.js' ).Strategy;
6+
var MultiSamlStrategy = require( '../lib/passport-saml/index.js' ).MultiSamlStrategy;
7+
8+
function verify () {}
9+
10+
describe('Strategy()', function() {
11+
it('extends passport Strategy', function() {
12+
function getSamlOptions () { return {} }
13+
var strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, verify);
14+
strategy.should.be.an.instanceOf(SamlStrategy);
15+
});
16+
17+
it('throws if wrong finder is provided', function() {
18+
function createStrategy (){ return new MultiSamlStrategy({}, verify) };
19+
should.throws(createStrategy);
20+
});
21+
});
22+
23+
describe('strategy#authenticate', function() {
24+
beforeEach(function() {
25+
this.superAuthenticateStub = sinon.stub(SamlStrategy.prototype, 'authenticate');
26+
});
27+
28+
afterEach(function() {
29+
this.superAuthenticateStub.restore();
30+
});
31+
32+
it('calls super with request and auth options', function(done) {
33+
var superAuthenticateStub = this.superAuthenticateStub;
34+
function getSamlOptions (req, fn) {
35+
fn();
36+
sinon.assert.calledOnce(superAuthenticateStub);
37+
done();
38+
};
39+
40+
var strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, verify);
41+
strategy.authenticate();
42+
});
43+
44+
it('passes options on to saml strategy', function(done) {
45+
var passportOptions = {
46+
passReqToCallback: true,
47+
authnRequestBinding: 'HTTP-POST',
48+
getSamlOptions: function (req, fn) {
49+
fn();
50+
strategy._passReqToCallback.should.eql(true);
51+
strategy._authnRequestBinding.should.eql('HTTP-POST');
52+
done();
53+
}
54+
};
55+
56+
var strategy = new MultiSamlStrategy(passportOptions, verify);
57+
strategy.authenticate();
58+
});
59+
60+
it('uses geted options to setup internal saml provider', function(done) {
61+
var samlOptions = {
62+
issuer: 'http://foo.issuer',
63+
callbackUrl: 'http://foo.callback',
64+
cert: 'deadbeef',
65+
host: 'lvh',
66+
acceptedClockSkewMs: -1,
67+
identifierFormat:
68+
'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
69+
path: '/saml/callback',
70+
logoutUrl: 'http://foo.slo',
71+
signatureAlgorithm: 'sha256'
72+
};
73+
74+
function getSamlOptions (req, fn) {
75+
fn(null, samlOptions);
76+
strategy._saml.options.should.containEql(samlOptions);
77+
done();
78+
}
79+
80+
var strategy = new MultiSamlStrategy(
81+
{ getSamlOptions: getSamlOptions },
82+
verify
83+
);
84+
strategy.authenticate();
85+
});
86+
});
87+
88+
describe('strategy#logout', function() {
89+
beforeEach(function() {
90+
this.superAuthenticateStub = sinon.stub(SamlStrategy.prototype, 'logout');
91+
});
92+
93+
afterEach(function() {
94+
this.superAuthenticateStub.restore();
95+
});
96+
97+
it('calls super with request and auth options', function(done) {
98+
var superAuthenticateStub = this.superAuthenticateStub;
99+
function getSamlOptions (req, fn) {
100+
fn();
101+
sinon.assert.calledOnce(superAuthenticateStub);
102+
done();
103+
};
104+
105+
var strategy = new MultiSamlStrategy({ getSamlOptions: getSamlOptions }, verify);
106+
strategy.logout();
107+
});
108+
109+
it('passes options on to saml strategy', function(done) {
110+
var passportOptions = {
111+
passReqToCallback: true,
112+
authnRequestBinding: 'HTTP-POST',
113+
getSamlOptions: function (req, fn) {
114+
fn();
115+
strategy._passReqToCallback.should.eql(true);
116+
strategy._authnRequestBinding.should.eql('HTTP-POST');
117+
done();
118+
}
119+
};
120+
121+
var strategy = new MultiSamlStrategy(passportOptions, verify);
122+
strategy.logout();
123+
});
124+
125+
it('uses geted options to setup internal saml provider', function(done) {
126+
var samlOptions = {
127+
issuer: 'http://foo.issuer',
128+
callbackUrl: 'http://foo.callback',
129+
cert: 'deadbeef',
130+
host: 'lvh',
131+
acceptedClockSkewMs: -1,
132+
identifierFormat:
133+
'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
134+
path: '/saml/callback',
135+
logoutUrl: 'http://foo.slo',
136+
signatureAlgorithm: 'sha256'
137+
};
138+
139+
function getSamlOptions (req, fn) {
140+
fn(null, samlOptions);
141+
strategy._saml.options.should.containEql(samlOptions);
142+
done();
143+
}
144+
145+
var strategy = new MultiSamlStrategy(
146+
{ getSamlOptions: getSamlOptions },
147+
verify
148+
);
149+
strategy.logout();
150+
});
151+
});

0 commit comments

Comments
 (0)