Skip to content

Commit d7c5a8e

Browse files
committed
validate id_token workflow
1 parent c2b8979 commit d7c5a8e

6 files changed

Lines changed: 306 additions & 64 deletions

File tree

src/JwtUtil.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,58 @@ import Log from './Log';
33
let __global;
44

55
try {
6+
// this is assuming we have a reference to
7+
// the jsrsasign library loaded in the browser
68
__global = window;
79
}
810
catch (e) {
9-
// for testing
11+
// this will certainly throw in our unit tests
12+
// we're assuming the use of the init() method
13+
// below for testing
1014
}
1115

1216
export default class JwtUtil {
1317

18+
// this is used to configure jsrsasign loaded via
19+
// require in our unit tests
1420
static init(global) {
1521
__global = global;
1622
}
1723

18-
static getAlg(jwt) {
19-
Log.info("JwtUtil.getAlg", jwt);
24+
static parseJwt(jwt) {
25+
Log.info("JwtUtil.parseJwt");
2026
try {
2127
var token = __global.jws.JWS.parse(jwt);
22-
return token.headerObj.alg;
28+
return {
29+
header: token.headerObj,
30+
payload : token.payloadObj
31+
}
2332
}
2433
catch (e) {
2534
Log.error(e);
2635
}
2736
}
28-
37+
38+
static validateJwtRsa(jwt, key) {
39+
Log.info("JwtUtil.validateJwtRsa");
40+
try {
41+
return true;
42+
}
43+
catch (e) {
44+
Log.error(e);
45+
}
46+
}
47+
48+
static validateJwtEc(jwt, key) {
49+
Log.info("JwtUtil.validateJwtEc");
50+
try {
51+
return true;
52+
}
53+
catch (e) {
54+
Log.error(e);
55+
}
56+
}
57+
2958
static hashString(value, alg) {
3059
Log.info("JwtUtil.hashString", value, alg);
3160
try {

src/MetadataService.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ export default class MetadataService {
3737
throw new Error("Failed to load metadata");
3838
});
3939
}
40+
41+
getIssuer() {
42+
Log.info("MetadataService.getIssuer");
43+
return this.getMetadataProperty("issuer");
44+
}
4045

4146
getAuthorizationEndpoint() {
4247
Log.info("MetadataService.getAuthorizationEndpoint");

src/ResponseValidator.js

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -181,63 +181,73 @@ export default class ResponseValidator {
181181
validateIdToken(state, response) {
182182
Log.info("ResponseValidator.validateIdToken");
183183

184-
return Promise.resolve(response);
185-
186-
// log("OidcClient.validateIdTokenAsync");
187-
188-
// var client = this;
189-
// var settings = client._settings;
190-
191-
// return client.loadX509SigningKeyAsync().then(function (cert) {
192-
193-
// var jws = new KJUR.jws.JWS();
194-
// if (jws.verifyJWSByPemX509Cert(id_token, cert)) {
195-
// var id_token_contents = JSON.parse(jws.parsedJWS.payloadS);
184+
if (!state.nonce) {
185+
Log.error("No nonce on state");
186+
return Promise.reject(new Error("No nonce on state"));
187+
}
196188

197-
// if (nonce !== id_token_contents.nonce) {
198-
// return error("Invalid nonce");
199-
// }
189+
let jwt = this._jwtUtil.parseJwt(response.id_token);
190+
if (!jwt || !jwt.header || !jwt.payload) {
191+
Log.error("Failed to parse id_token", jwt);
192+
return Promise.reject(new Error("Failed to parse id_token"));
193+
}
200194

201-
// return client.loadMetadataAsync().then(function (metadata) {
195+
var kid = jwt.header.kid;
196+
if (!kid) {
197+
Log.error("No kid found in id_token");
198+
return Promise.reject(new Error("No kid found in id_token"));
199+
}
202200

203-
// if (id_token_contents.iss !== metadata.issuer) {
204-
// return error("Invalid issuer");
205-
// }
201+
let audience = this._settings.client_id;
202+
if (!audience) {
203+
Log.error("Invalid audience/client_id value");
204+
return Promise.reject(new Error("Invalid audience/client_id value"));
205+
}
206206

207-
// if (id_token_contents.aud !== settings.client_id) {
208-
// return error("Invalid audience");
209-
// }
207+
return this._metadataService.getIssuer().then(issuer => {
208+
Log.info("Received issuer");
210209

211-
// var now = parseInt(Date.now() / 1000);
210+
return this._metadataService.getSigningKeys().then(keys => {
211+
if (!keys){
212+
Log.error("No signing keys from metadata");
213+
return Promise.reject(new Error("No signing keys from metadata"));
214+
}
215+
216+
Log.info("Received signing keys");
212217

213-
// // accept tokens issues up to 5 mins ago
214-
// var diff = now - id_token_contents.iat;
215-
// if (diff > (5 * 60)) {
216-
// return error("Token issued too long ago");
217-
// }
218+
let key = keys.filter(key => {
219+
return key.kid === kid;
220+
})[0];
218221

219-
// if (id_token_contents.exp < now) {
220-
// return error("Token expired");
221-
// }
222+
if (!key) {
223+
Log.error("No key matching kid found in signing keys");
224+
return Promise.reject(new Error("No key matching kid found in signing keys"));
225+
}
222226

223-
// if (access_token && settings.load_user_profile) {
224-
// // if we have an access token, then call user info endpoint
225-
// return client.loadUserProfile(access_token, id_token_contents).then(function (profile) {
226-
// return copy(profile, id_token_contents);
227-
// });
228-
// }
229-
// else {
230-
// // no access token, so we have all our claims
231-
// return id_token_contents;
232-
// }
227+
if (!this.validateJwt(response.id_token, key, issuer, audience)) {
228+
Log.error("Signature failed to validate");
229+
return Promise.reject(new Error("Signature failed to validate"));
230+
}
233231

234-
// });
235-
// }
236-
// else {
237-
// return error("JWT failed to validate");
238-
// }
232+
response.profile = jwt.payload;
233+
return response;
234+
});
235+
});
236+
}
239237

240-
// });
238+
validateJwt(id_token, key, issuer, audience) {
239+
Log.info("ResponseValidator.validateJwt");
240+
241+
if (key.kty === "RSA") {
242+
return this._jwtUtil.validateJwtRsa(id_token, key, issuer, audience);
243+
}
244+
else if (key.kty === "EC") {
245+
return this._jwtUtil.validateJwtEc(id_token, key, issuer, audience);
246+
}
247+
else {
248+
Log.error("Unsupported key type:", key.kty);
249+
return false;
250+
}
241251
}
242252

243253
validateAccessToken(response) {
@@ -258,7 +268,13 @@ export default class ResponseValidator {
258268
return Promise.reject(new Error("No id_token"));
259269
}
260270

261-
var hashAlg = this._jwtUtil.getAlg(response.id_token);
271+
let jwt = this._jwtUtil.parseJwt(response.id_token);
272+
if (!jwt || !jwt.header) {
273+
Log.error("Failed to parse id_token", jwt);
274+
return Promise.reject(new Error("Failed to parse id_token"));
275+
}
276+
277+
var hashAlg = jwt.header.alg;
262278
if (!hashAlg || hashAlg.length !== 5) {
263279
Log.error("Unsupported alg:", hashAlg);
264280
return Promise.reject(new Error("Unsupported alg: " + hashAlg));

test/MetadataService.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,23 @@ describe("MetadataService", function() {
220220

221221
});
222222

223+
describe("getIssuer", function() {
224+
225+
it("should return value from", function(done) {
226+
settings.metadata = {
227+
issuer: "http://sts"
228+
};
229+
230+
let p = subject.getIssuer();
231+
232+
p.then(result => {
233+
result.should.equal("http://sts");
234+
done();
235+
});
236+
});
237+
238+
});
239+
223240
describe("getSigningKeys", function() {
224241

225242
it("should return a promise", function() {

0 commit comments

Comments
 (0)