oidc-client
Version:
OpenID Connect (OIDC) & OAuth2 client library
148 lines (125 loc) • 5.93 kB
JavaScript
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
import { JsonService } from './JsonService.js';
import { MetadataService } from './MetadataService.js';
import { Log } from './Log.js';
import { JoseUtil } from './JoseUtil.js';
export class UserInfoService {
constructor(
settings,
JsonServiceCtor = JsonService,
MetadataServiceCtor = MetadataService,
joseUtil = JoseUtil
) {
if (!settings) {
Log.error("UserInfoService.ctor: No settings passed");
throw new Error("settings");
}
this._settings = settings;
this._jsonService = new JsonServiceCtor(undefined, undefined, this._getClaimsFromJwt.bind(this));
this._metadataService = new MetadataServiceCtor(this._settings);
this._joseUtil = joseUtil;
}
getClaims(token) {
if (!token) {
Log.error("UserInfoService.getClaims: No token passed");
return Promise.reject(new Error("A token is required"));
}
return this._metadataService.getUserInfoEndpoint().then(url => {
Log.debug("UserInfoService.getClaims: received userinfo url", url);
return this._jsonService.getJson(url, token).then(claims => {
Log.debug("UserInfoService.getClaims: claims received", claims);
return claims;
});
});
}
_getClaimsFromJwt(req) {
try {
let jwt = this._joseUtil.parseJwt(req.responseText);
if (!jwt || !jwt.header || !jwt.payload) {
Log.error("UserInfoService._getClaimsFromJwt: Failed to parse JWT", jwt);
return Promise.reject(new Error("Failed to parse id_token"));
}
var kid = jwt.header.kid;
let issuerPromise;
switch (this._settings.userInfoJwtIssuer) {
case 'OP':
issuerPromise = this._metadataService.getIssuer();
break;
case 'ANY':
issuerPromise = Promise.resolve(jwt.payload.iss);
break;
default:
issuerPromise = Promise.resolve(this._settings.userInfoJwtIssuer);
break;
}
return issuerPromise.then(issuer => {
Log.debug("UserInfoService._getClaimsFromJwt: Received issuer:" + issuer);
return this._metadataService.getSigningKeys().then(keys => {
if (!keys) {
Log.error("UserInfoService._getClaimsFromJwt: No signing keys from metadata");
return Promise.reject(new Error("No signing keys from metadata"));
}
Log.debug("UserInfoService._getClaimsFromJwt: Received signing keys");
let key;
if (!kid) {
keys = this._filterByAlg(keys, jwt.header.alg);
if (keys.length > 1) {
Log.error("UserInfoService._getClaimsFromJwt: No kid found in id_token and more than one key found in metadata");
return Promise.reject(new Error("No kid found in id_token and more than one key found in metadata"));
}
else {
// kid is mandatory only when there are multiple keys in the referenced JWK Set document
// see http://openid.net/specs/openid-connect-core-1_0.html#Signing
key = keys[0];
}
}
else {
key = keys.filter(key => {
return key.kid === kid;
})[0];
}
if (!key) {
Log.error("UserInfoService._getClaimsFromJwt: No key matching kid or alg found in signing keys");
return Promise.reject(new Error("No key matching kid or alg found in signing keys"));
}
let audience = this._settings.client_id;
let clockSkewInSeconds = this._settings.clockSkew;
Log.debug("UserInfoService._getClaimsFromJwt: Validaing JWT; using clock skew (in seconds) of: ", clockSkewInSeconds);
return this._joseUtil.validateJwt(req.responseText, key, issuer, audience, clockSkewInSeconds, undefined, true).then(() => {
Log.debug("UserInfoService._getClaimsFromJwt: JWT validation successful");
return jwt.payload;
});
});
});
return;
}
catch (e) {
Log.error("UserInfoService._getClaimsFromJwt: Error parsing JWT response", e.message);
reject(e);
return;
}
}
_filterByAlg(keys, alg) {
var kty = null;
if (alg.startsWith("RS")) {
kty = "RSA";
}
else if (alg.startsWith("PS")) {
kty = "PS";
}
else if (alg.startsWith("ES")) {
kty = "EC";
}
else {
Log.debug("UserInfoService._filterByAlg: alg not supported: ", alg);
return [];
}
Log.debug("UserInfoService._filterByAlg: Looking for keys that match kty: ", kty);
keys = keys.filter(key => {
return key.kty === kty;
});
Log.debug("UserInfoService._filterByAlg: Number of keys that match kty: ", kty, keys.length);
return keys;
}
}