Skip to content
This repository was archived by the owner on Feb 26, 2020. It is now read-only.

Commit 654abe2

Browse files
committed
Updated to work with B2C flows.
1 parent 2a635bd commit 654abe2

3 files changed

Lines changed: 130 additions & 65 deletions

File tree

node-server/lib/metadata.js

Lines changed: 90 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var xml2js = require('xml2js');
2121
var request = require('request');
2222
var aadutils = require('./aadutils');
2323
var async = require('async');
24+
var ursa = require('ursa');
2425

2526
// Logging
2627

@@ -102,20 +103,56 @@ Metadata.prototype.updateSamlMetadata = function(doc, next) {
102103
Metadata.prototype.updateOidcMetadata = function(doc, next) {
103104
log.info('Request to update the Open ID Connect Metadata');
104105
try {
105-
this.oidc = {};
106-
107-
this.oidc.issuer = doc['issuer'];
108-
this.oidc.keyURL = doc['jwks_uri'];
109-
this.oidc.algorithm = doc['id_token_signing_alg_values_supported'];
110-
111-
log.info("algorithm we will use is: ", this.oidc.algorithm);
112-
log.info("the Keys are located at: ", this.oidc.keyURL);
106+
this.oidc = {};
107+
this.oidc.issuer = doc['issuer'];
108+
this.oidc.algorithms = doc['id_token_signing_alg_values_supported'];
109+
var jwksUri = doc.jwks_uri;
110+
111+
log.info('Algorithm retreived was: ', this.oidc.algorithms);
112+
log.info('Issuer we are using is: ', this.oidc.issuer);
113+
log.info('Key Endpoint will we use is: ', jwksUri);
114+
115+
var self = this;
116+
var callback = next;
117+
118+
async.waterfall([
119+
// fetch the signing keys
120+
function(next){
121+
request(jwksUri, function (err, response, body) {
122+
if(err) {
123+
next(err);
124+
} else if(response.statusCode !== 200) {
125+
next(new Error("Error:" + response.statusCode + " Cannot get AAD Signing Keys"));
126+
} else {
127+
next(null, body);
128+
}
129+
});
130+
},
131+
132+
function(body, next){
133+
// parse the AAD Federation metadata xml
134+
log.info("Parsing JSON retreived from the signing keys endpoint.");
135+
try {
136+
self.oidc.keys = JSON.parse(body).keys;
137+
log.info("***** KEYS ******");
138+
log.info("");
139+
log.info(self.oidc.keys);
140+
log.info("");
141+
log.info("***********");
142+
next(null);
143+
} catch (e) {
144+
next(new Error(e));
145+
}
146+
},
147+
148+
], function (err) {
149+
callback(err);
150+
});
113151

114-
next(null);
115-
} catch (e) {
116-
next(new Error('Invalid Open ID Connect Federation Metadata ' + e.message));
117-
}
118-
};
152+
} catch (e) {
153+
next(new Error('Invalid Open ID Connect Federation Metadata ' + e.message));
154+
}
155+
};
119156

120157

121158
Metadata.prototype.updateWsfedMetadata = function(doc, next) {
@@ -147,6 +184,24 @@ Metadata.prototype.updateWsfedMetadata = function(doc, next) {
147184
}
148185
};
149186

187+
Metadata.prototype.generateOidcPEM = function(kid) {
188+
if (!this.oidc.keys) {
189+
return null;
190+
}
191+
for (var i=0; i < this.oidc.keys.length; i++) {
192+
if (this.oidc.keys[i].kid == kid) {
193+
log.info('Working on key: ', this.oidc.keys[i])
194+
var modulus = new Buffer(this.oidc.keys[i].n, 'base64');
195+
var exponent = new Buffer(this.oidc.keys[i].e, 'base64');
196+
197+
var pubKey = ursa.createPublicKeyFromComponents(modulus, exponent);
198+
return pubKey.toPublicPem('utf8');
199+
}
200+
}
201+
return null;
202+
};
203+
204+
150205
Metadata.prototype.fetch = function(callback) {
151206
var self = this;
152207
log.info("Fetching metadata from the provided metadata URL: " + self.url);
@@ -159,7 +214,7 @@ Metadata.prototype.fetch = function(callback) {
159214
} else if(response.statusCode !== 200) {
160215
next(new Error("Error:" + response.statusCode + " Cannot get AAD Federation metadata from " + self.url));
161216
} else {
162-
log.info("***********");
217+
log.info("***** METADATA ******");
163218
log.info("");
164219
log.info(body);
165220
log.info("");
@@ -185,29 +240,30 @@ Metadata.prototype.fetch = function(callback) {
185240

186241
} else if(self.authtype == "oidc") {
187242
log.info("Parsing JSON retreived from the endpoint");
188-
self.metadata = JSON.parse(body);
243+
self.metadata = JSON.parse(body);
244+
next(null);
189245

190246
} else { next(new Error("No Authentication type specified to metadata parser. Valid types are saml, wsfed, or odic")); }
191247

192248
},
193-
function(next){
194-
if(self.authtype == "saml") {
195-
// update the SAML SSO endpoints and certs from the metadata
196-
self.updateSamlMetadata(self.metatdata, next);
197-
}},
198-
function(next){
199-
if(self.authtype == "wsfed") {
200-
// update the SAML SSO endpoints and certs from the metadata
201-
self.updateWsfedMetadata(self.metatdata, next);
202-
}},
203-
function(next){
204-
if(self.authtype == "oidc") {
205-
self.updateOidcMetadata(self.metadata, next);
206-
}}
207-
], function (err) {
208-
// return err or success (err === null) to callback
209-
callback(err);
210-
});
211-
};
249+
function(next){
250+
251+
console.log('updating metadata...');
252+
253+
if(self.authtype == "saml") {
254+
self.updateSamlMetadata(self.metatdata, next);
255+
}
256+
else if(self.authtype == "wsfed") {
257+
self.updateWsfedMetadata(self.metatdata, next);
258+
}
259+
else if(self.authtype == "oidc") {
260+
self.updateOidcMetadata(self.metadata, next);
261+
}
262+
},
263+
], function (err) {
264+
callback(err);
265+
log.info('Goodbye from Metadata');
266+
});
267+
};
212268

213269
exports.Metadata = Metadata;

node-server/lib/oidc_strategy.js

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ var async = require('async');
2828
var jwt = require('jsonwebtoken');
2929
var request = require('request');
3030
var Metadata = require('./metadata').Metadata;
31+
var ursa = require('ursa');
32+
var jws = require('jws');
3133

3234
var log = bunyan.createLogger({name: 'Microsoft OpenID Connect: Passport Strategy'});
3335
/**
@@ -88,7 +90,7 @@ if(options.publicCert) {
8890

8991
if(options.metadataurl) {
9092

91-
log.info(options.metadataurl, 'metadata url provided to Strategy');
93+
log.info('Metadata url provided to Strategy was: ', options.metadataurl);
9294
this.metadata = new Metadata(options.metadataurl, "oidc");
9395
}
9496

@@ -97,45 +99,30 @@ if (!options.certificate && !options.metadataurl) {
9799
throw new TypeError('OIDCBearerStrategy requires either a PEM encoded public key or a metadata location that contains cert data for RSA and ECDSA callback.');
98100
}
99101

102+
if (typeof options == 'function') {
103+
verify = options;
104+
options = {};
105+
}
100106

101107
// Passport requires a verify function
102108

103109
if (!verify) {
104110
throw new TypeError('OIDCBearerStrategy requires a verify callback. Do not cheat!');
105111
}
106112

107-
this.certs = [];
108-
109113
// Token validation settings. Hopefully most of these will be pulled from the metadata and this is not needed
110114

111115

112-
113-
// fetch metadata
114-
115-
if(this.metadata) {
116116
this.metadata.fetch(function(err) {
117-
if(err) {
118-
log.warn('Error parsing metadata.', err);
119-
return err;
120-
} else {
121-
log.info(this.metadata,'Metadata returned');
122-
this.oidc = self.metadata.oidc;
123-
this.keyURL = oidc.keyURL;
124-
this.algothims = oidc.algorithm;
125-
}
126-
}); };
127-
128-
// fetch keys
129-
130-
117+
if (err) {
118+
throw new Error("Unable to fetch metadata: " + err);
119+
}
131120

132-
var config = {
133-
// The URL of the metadata document for your app. We will put the keys for token validation from the URL found in the jwks_uri tag of the in the metadata.
134-
algorithms: this.algorithms
121+
});
135122

136-
};
137123

138124
function jwtVerify(req, token, done) {
125+
139126
if (!options.passReqToCallback) {
140127
token = arguments[0];
141128
done = arguments[1];
@@ -144,11 +131,30 @@ algorithms: this.algorithms
144131
}
145132

146133

134+
var decoded = jws.decode(token);
135+
if (decoded == null) {
136+
done(null, false, "Invalid JWT token.");
137+
}
138+
139+
log.info(decoded, 'was token decrypted. But is it valid?');
140+
147141

148-
var PEMkey = pem.certToPEM(this._oidc.certs[0]);
149-
log.info(PEMkey, 'was the PEM returned');
142+
// We have two different types of token signatures we have to validate here. One provides x5t and the other a kid. We need to call the right one.
143+
if (decoded.header.x5t) {
144+
var PEMkey = this.metadata.generateOidcPEM(decoded.header.x5t);
145+
}
146+
else if (decoded.header.kid) {
147+
var PEMkey = this.metadata.generateOidcPEM(decoded.header.x5t);
148+
}
149+
else { throw new TypeError('We did not reveive a token we know how to validate');
150+
}
150151

151-
jwt.verify(token, PEMkey, config, function (err, token) {
152+
153+
154+
options.issuer = this.metadata.oidc.issuer;
155+
options.algorithms = ['RS256'];
156+
157+
jwt.verify(token, PEMkey, options, function (err, token) {
152158
if (err) {
153159
if (err instanceof jwt.TokenExpiredError) {
154160
log.warn("Access token expired");
@@ -176,6 +182,10 @@ algorithms: this.algorithms
176182
});
177183
}
178184

185+
var opts = {};
186+
opts.passReqToCallback = true;
187+
188+
console.log('Req: ' + options.passReqToCallback);
179189

180190
BearerStrategy.call(this, options, jwtVerify);
181191

node-server/test/metadata_tests.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ exports['metadata'] = {
119119
var m = new Metadata(oidc_metadataUrl, "oidc"); // this indicates to parse JSON vs. XML
120120
m.fetch(function(err) {
121121
test.ifError(err, 'method had error before testing properties');
122-
test.ok(m.oidc.certs.length > 0, 'fetch should obtain 1 or more saml x509 certificates');
123-
test.ok(m.oidc.loginEndpoint, 'fetch should obtain saml login endpoint');
124-
test.ok(m.oidc.logoutEndpoint, 'fetch should obtain saml logout endpoint');
122+
test.ok(m.oidc.algorithm, 'fetch should obtain odic algorithm');
123+
test.ok(m.oidc.keyURL, 'fetch should obtain odic Key URL');
125124
});
126125
},
127126
Error,

0 commit comments

Comments
 (0)