Socket
Socket
Sign inDemoInstall

@sap/xssec

Package Overview
Dependencies
Maintainers
1
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/xssec - npm Package Compare versions

Comparing version 3.2.17 to 3.2.18

lib/jwks/Jwk.js

4

CHANGELOG.md
# Change Log
All notable changes to this project will be documented in this file.
## 3.2.18 - 2023-07-13
- Replaced keycache implementation with new JwksReplica implementation: When a JWKS is used for validation, it is now checked if the cached replica will expire soon (default refresh period: 15min before expiration). If so, it will be refreshed in the background and the cached replica will still be used for validation until it has expired completely (default expiration time: 30min since last refresh). Validation of incoming requests will only be blocked by an expired JWKS if it could not be refreshed during the refresh period. Only one call at a time will be performed to refresh the JWKS.
- Addd support for resource-attribute for token flow/token exchange calls
## 3.2.17 - 2023-01-09

@@ -5,0 +9,0 @@ - obfuscate credentials in debug output, when xssec:request debug variable is set

21

lib/constants.js

@@ -81,23 +81,2 @@ // Scope Prefix:

Object.defineProperty(exports, "KEYCACHE_DEFAULT_TOKENKEY_PATH", {
value: 'token_keys',
enumerable: true,
writable: false,
configurable: false
});
Object.defineProperty(exports, "KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES", {
value: 10,
enumerable: true,
writable: false,
configurable: false
});
Object.defineProperty(exports, "KEYCACHE_DEFAULT_CACHE_SIZE", {
value: 1000,
enumerable: true,
writable: false,
configurable: false
});
Object.defineProperty(exports, "USER_AGENT", {

@@ -104,0 +83,0 @@ value: "nodejs-xssec-3",

@@ -7,6 +7,4 @@ 'use strict';

var debugError = debug('xssec:securitycontext');
const {JwtTokenValidatorIAS} = require('../validator')
const createJwtTokenValidator = require('../validator').createJwtTokenValidator;
const VerificationKey = require('../verificationkey');
debugError.log = console.error.bind(console);

@@ -59,6 +57,5 @@ debugTrace.log = console.log.bind(console);

this.verifyToken = function (encodedToken, attributes, cb) {
var verificationKey = new VerificationKey(config, "IAS", attributes);
var jwtValidator = createJwtTokenValidator(verificationKey, configArr, config);
const validator = new JwtTokenValidatorIAS(configArr, config);
jwtValidator.validateToken(encodedToken, function (err, tokenInfo) {
validator.validateToken(encodedToken, function (err, tokenInfo) {
if (err) {

@@ -65,0 +62,0 @@ try {

'use strict';
const constants = require('../constants');
const requests = require('../requests');
const {JwtTokenValidatorUAA} = require('../validator')

@@ -10,5 +11,2 @@ // use environment variable DEBUG with value 'xssec:*' for trace/error messages

const createJwtTokenValidator = require('../validator').createJwtTokenValidator;
const VerificationKey = require('../verificationkey');
debugError.log = console.error.bind(console);

@@ -342,15 +340,7 @@ debugTrace.log = console.log.bind(console);

this.verifyToken = function (encodedToken, attributes, cb) {
var verificationKey = new VerificationKey(config, this.getConfigType(), attributes);
var jwtValidator = createJwtTokenValidator(verificationKey, configArr, config);
const validator = new JwtTokenValidatorUAA(configArr, config);
jwtValidator.validateToken(encodedToken, function (err, tokenInfo) {
validator.validateToken(encodedToken, function (err, tokenInfo) {
if (err) {
try {
if(verificationKey.getLoadKeyError()) {
//The error happens during loading of the key.
//so the tokenValidationError is normal as the fallback will not work in most cases.
//For better analysis take the original error
err = verificationKey.getLoadKeyError();
}
cb(err, null, tokenInfo);

@@ -365,3 +355,3 @@ } catch(e) {

isForeignMode = jwtValidator.isForeignMode();
isForeignMode = validator.isForeignMode();

@@ -368,0 +358,0 @@ //Token is now validated. So just fill local variables

'use strict';
const constants = require('../constants');
const requests = require('../requests');
const {JwtTokenValidatorXSUAA} = require('../validator')

@@ -10,4 +11,2 @@ // use environment variable DEBUG with value 'xssec:*' for trace/error messages

const createJwtTokenValidator = require('../validator').createJwtTokenValidator;
const VerificationKey = require('../verificationkey');

@@ -342,15 +341,7 @@ debugError.log = console.error.bind(console);

this.verifyToken = function (encodedToken, attributes, cb) {
var verificationKey = new VerificationKey(config, this.getConfigType(), attributes);
var jwtValidator = createJwtTokenValidator(verificationKey, configArr, config);
const validator = new JwtTokenValidatorXSUAA(configArr, config);
jwtValidator.validateToken(encodedToken, function (err, tokenInfo) {
validator.validateToken(encodedToken, function (err, tokenInfo) {
if (err) {
try {
if(verificationKey.getLoadKeyError()) {
//The error happens during loading of the key.
//so the tokenValidationError is normal as the fallback will not work in most cases.
//For better analysis take the original error
err = verificationKey.getLoadKeyError();
}
cb(err, null, tokenInfo);

@@ -365,3 +356,3 @@ } catch(e) {

isForeignMode = jwtValidator.isForeignMode();
isForeignMode = validator.isForeignMode();

@@ -368,0 +359,0 @@ //Token is now validated. So just fill local variables

@@ -24,2 +24,25 @@ 'use strict';

function isXSUAAConfig(config) {
return (config.xsappname ? true : (process.env.XSAPPNAME ? true : false));
}
function toFormArray(obj) {
let ret = [];
for (let n in obj) {
const val = obj[n];
if (val !== null && val !== undefined) {
if (Array.isArray(val)) {
for (let i = 0; i < val.length; ++i) {
ret.push([n, val[i]]);
}
} else if (typeof val === 'object') {
ret.push([n, JSON.stringify(val)]);
} else {
ret.push([n, val]);
}
}
}
return ret;
}
async function _requestToNetworkAXIOS(fnc, options, cb) {

@@ -40,2 +63,6 @@ if (debugTrace.enabled) {

if (opt.form && opt.form.assertion) {
opt.form.assertion = "<Assertion>"
}
debugTrace(fnc + '::HTTP Call with %O', opt);

@@ -54,3 +81,3 @@ }

if (options.form) {
axios_options.data = new url.URLSearchParams(options.form).toString();
axios_options.data = new url.URLSearchParams(toFormArray(options.form)).toString();
}

@@ -127,4 +154,3 @@

if (additionalAttributes !== null) {
var authorities = { "az_attr": additionalAttributes };
options.form.authorities = authorities;
options.form.authorities = { "az_attr": additionalAttributes };
}

@@ -149,5 +175,7 @@ }

// jwt bearer flow
const path = attributes.configType === 'ias' ? '/oauth2/token' : '/oauth/token';
const options = {
method: 'POST',
url: url + '/oauth/token',
url: url + path,
headers: DefaultHeaders(zoneId, serviceCredentials),

@@ -162,2 +190,6 @@ form: {

if (attributes.resource) {
options.form.resource = attributes.resource;
}
if (serviceCredentials.certificate) {

@@ -258,3 +290,8 @@ options.https = {

if (config.type) {
config.type = config.type.toLowerString();
}
return {
configType: config.type ? config.type : (isXSUAAConfig(config.credentials) ? "xsuaa" : "ias"),
scopes: config.scopes,

@@ -264,3 +301,4 @@ correlationId: config.correlationId,

username: config.username,
password: config.password
password: config.password,
resource: config.resource
};

@@ -270,3 +308,4 @@ }

return {
timeout: defaultTimeout
timeout: defaultTimeout,
configType: isXSUAAConfig(config) ? "xsuaa" : "ias"
};

@@ -273,0 +312,0 @@ }

@@ -131,5 +131,5 @@ 'use strict';

this.verify = function (getKeyCBOrValue, cb) {
this.verify = function (verificationKeySupplier, cb) {
return jwt.verify(encoded,
getKeyCBOrValue,
verificationKeySupplier,
{

@@ -136,0 +136,0 @@ algorithms: ['RS256'] //XSUAA currently only allow/generate RS256

@@ -6,15 +6,16 @@ 'use strict';

const debugTrace = debug('xssec:validators');
debugTrace.log = console.log.bind(console);
const debugError = debug('xssec:validators');
debugError.log = console.error.bind(console);
const jwt = require('jsonwebtoken');
const url = require('url');
const TokenInfo = require('./tokeninfo');
const TokenExchanger = require('./tokenexchanger');
const JwksManager = require('./jwks/JwksManager');
const JwksReplica = require('./jwks/JwksReplica');
let jwksManager;
const requests = require('./requests');
const url = require('url');
const DOT = ".";
const ValidationResults = new function () {

@@ -80,3 +81,3 @@ function Result(suc, desc) {

return cidFromToken.trim() === clientId.trim();
}
}

@@ -167,3 +168,18 @@ //iterate over all configured clientIds and return true of the cb returns true

function JwtTokenValidatorIAS(verificationKey, configArray, serviceCredentials) {
function buildJwksManager(config) {
if(!config) {
return new JwksManager();
}
let jwksExpirationTime = config.expirationTime || JwksReplica.DEFAULT_EXPIRATION_TIME;
let jwksRefreshPeriod = config.refreshPeriod || JwksReplica.DEFAULT_REFRESH_PERIOD;
return new JwksManager().withExpirationTime(jwksExpirationTime).withRefreshPeriod(jwksRefreshPeriod);
}
function JwtTokenValidatorIAS(configArray, serviceCredentials) {
if(!jwksManager) {
jwksManager = buildJwksManager(serviceCredentials.jwksCache);
}
this.isForeignMode = function () {

@@ -228,3 +244,15 @@ return false;

return token.verify(verificationKey.getCallback(token), function(err, token) {
const verificationKeySupplier = async (header, callback) => {
let err, jwk;
try {
const jwks = await jwksManager.getIdentityJwks(issuer, token.getZoneId());
jwk = await jwks.get(header.kid);
} catch(e) {
err = e;
}
callback(err, jwk ? jwk.value : null);
}
return token.verify(verificationKeySupplier, function(err, token) {
if (err) {

@@ -254,3 +282,17 @@ debugError('\n' + err.message);

function JwtTokenValidatorXSUAA(verificationKey, configArray, serviceCredentials) {
function validateJku(jkuUrl, uaaDomain) {
if (!uaaDomain) {
throw new Error("JKU could not be validated because attribute \'uaadomain\' is missing from service credentials.");
}
var tokenKeyUrl = url.parse(jkuUrl);
if (tokenKeyUrl.hostname.substring(tokenKeyUrl.hostname.indexOf(uaaDomain), tokenKeyUrl.hostname.length) !== uaaDomain) {
throw new Error(`JKU of JWT token (${jkuUrl}) does not match UAA domain (${uaaDomain}).`);
}
}
function JwtTokenValidatorXSUAA(configArray, serviceCredentials) {
if(!jwksManager) {
jwksManager = buildJwksManager(serviceCredentials.jwksCache);
}
var foreignMode = false;

@@ -281,5 +323,31 @@

return token.verify(verificationKey.getCallback(token),
const verificationKeySupplier = async (header, callback) => {
let keyFromConfig = serviceCredentials.verificationkey;
if(!header.jku || !header.kid || header.kid == 'legacy-token-key') {
debugTrace("Token header contained no JKU or KID or the KID was 'legacy-token-key'. Using verification key from service configuration.");
return callback(null, keyFromConfig);
}
try {
validateJku(header.jku, serviceCredentials.uaadomain);
} catch(e) {
debugTrace("Using verification key from service configuration because JKU validation failed.", e.toString());
return callback(null, keyFromConfig);
}
let err, jwk;
try {
const jwks = jwksManager.getXsuaaJwks(header.jku, token.getZoneId());
jwk = await jwks.get(header.kid);
} catch(e) {
err = e;
}
return callback(err, jwk ? jwk.value : null);
}
return token.verify(verificationKeySupplier,
function (err, token) {
if (err) {
if (err) {
debugError(err.statuscode);

@@ -333,3 +401,6 @@ debugError(err.message);

function JwtTokenValidatorUAA(verificationKey, configArray, serviceCredentials) {
function JwtTokenValidatorUAA(configArray, serviceCredentials) {
if(!jwksManager) {
jwksManager = buildJwksManager(serviceCredentials.jwksCache);
}
var foreignMode = false;

@@ -360,3 +431,29 @@

return token.verify(verificationKey.getCallback(token),
const verificationKeySupplier = async (header, callback) => {
let keyFromConfig = serviceCredentials.verificationkey;
if(!header.jku || !header.kid || header.kid == 'legacy-token-key') {
debugTrace("Token header contained no JKU or KID or the KID was 'legacy-token-key'. Using verification key from service configuration.");
return callback(null, keyFromConfig);
}
try {
validateJku(header.jku, serviceCredentials.uaadomain);
} catch(e) {
debugTrace("Using verification key from service configuration because JKU validation failed.", e.toString());
return callback(null, keyFromConfig);
}
let err, jwk;
try {
const jwks = jwksManager.getXsuaaJwks(header.jku, token.getZoneId());
jwk = await jwks.get(header.kid);
} catch(e) {
err = e;
}
return callback(err, jwk ? jwk.value : null);
}
return token.verify(verificationKeySupplier,
function (err, token) {

@@ -412,15 +509,7 @@ if (err) {

function createJWTTokenValidator(verificationKey, configArray, serviceCredentials) {
if(verificationKey.getType() === "XSUAA") {
return new JwtTokenValidatorXSUAA(verificationKey, configArray, serviceCredentials);
} else if(verificationKey.getType() === "UAA") {
return new JwtTokenValidatorUAA(verificationKey, configArray, serviceCredentials);
} else {
return new JwtTokenValidatorIAS(verificationKey, configArray, serviceCredentials);
}
}
module.exports = {
JwtAudienceValidator: JwtAudienceValidator,
createJwtTokenValidator: createJWTTokenValidator
JwtTokenValidatorIAS: JwtTokenValidatorIAS,
JwtTokenValidatorUAA: JwtTokenValidatorUAA,
JwtTokenValidatorXSUAA: JwtTokenValidatorXSUAA,
JwtAudienceValidator: JwtAudienceValidator
};

@@ -42,32 +42,2 @@ 'use strict';

}
if (!config.keyCache) {
config.keyCache = {
expirationTime: constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES,
cacheSize: constants.KEYCACHE_DEFAULT_CACHE_SIZE
};
debugTrace("Using KeyCache with default values %o", config.keyCache);
} else {
if (config.keyCache.expirationTime) {
//if it's provided it has to be a number bigger than zero
if (typeof config.keyCache.expirationTime !== 'number' || config.keyCache.expirationTime < constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES) {
debugError("keyCache.expirationTime has to be a Number with the value of at least " + constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES + ". taking default value.");
config.keyCache.expirationTime = constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES;
}
} else {
config.keyCache.expirationTime = constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES;
}
if (config.keyCache.cacheSize) {
//if it's provided it has to be a number bigger than zero
if (typeof config.keyCache.cacheSize !== 'number' || config.keyCache.cacheSize < constants.KEYCACHE_DEFAULT_CACHE_SIZE) {
debugError("keyCache.cacheSize has to be a Number of at least " + constants.KEYCACHE_DEFAULT_CACHE_SIZE + ". taking default value");
config.keyCache.cacheSize = constants.KEYCACHE_DEFAULT_CACHE_SIZE;
}
} else {
config.keyCache.cacheSize = constants.KEYCACHE_DEFAULT_CACHE_SIZE;
}
debugTrace("Using KeyCache with custom values %o", config.keyCache);
}
}

@@ -74,0 +44,0 @@

{
"name": "@sap/xssec",
"version": "3.2.17",
"version": "3.2.18",
"description": "XS Advanced Container Security API for node.js",

@@ -30,5 +30,7 @@ "main": "./lib",

"jwt-decode": "^3.1.2",
"mocha": "^5.2.0",
"mocha": "^8.0.0",
"node-forge": "^1.3.0",
"should": "^13.2.1"
"should": "^13.2.1",
"sinon": "^14.0.0",
"convert": "^4.12.0"
},

@@ -35,0 +37,0 @@ "dependencies": {

@@ -98,30 +98,2 @@ @sap/xssec: XS Advanced Container Security API for node.js

### Disable the cache for the current call (ONLY FOR TESTING!)
The xssec library internally calls REST APIs to fetch the public verification keys from XSUAA/IAS.
For performance reasons there is a cache, so not all calls have to fetch the key again.
Now it's possible to turn off the cache using the `disableCache` option during context creation.
For this you have to restructe the configuration object.
```js
const config = {
credentials: xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa,
correlationId: "1111-1111-11111111"
disableCache: true
};
//now you can call the createSecurityContext method as always
//internally no Cache will be used!
xssec.createSecurityContext(access_token, config, function(error, securityContext, tokenInfo) {
if (error) {
console.log('Security Context creation failed');
return;
}
console.log('Security Context created successfully');
console.log(tokenInfo.getPublicClaims());
});
```
This also works for the Tokenexchange methods!
### Usage with Passport Strategy

@@ -174,40 +146,49 @@

### Configure the cache of Verificationkeys
For token verification the library needs a so called `public key`. This key can be requested from the XSUAA server.
The library caches these keys to reduce the load to the XSUAA. (And for better performance!)
There are two values that are used to control the cache. The number of cache entries and an expiration time of each item. The latter is important to easily support key rotation scenarios and should not be too high.
:exclamation: **Normally you don't need to overwrite the default values!**
### JWKS cache configuration
To verify the validity of a token, the library needs to ensure that it was signed with a `public key` from the authorization server's [JWKS](https://datatracker.ietf.org/doc/html/rfc7517) (JSON Web Key Set). The application retrieves the JWKS via HTTP from the authorization server. It is cached to reduce both the load on the authorization server and the latency of requests introduced by the token validation.
But in rare situations there is a need to change them.
There are two values that are used to control the cache:
- `expiration time`: When a JWKS is needed for validation whose cache entry has expired (`time since last refresh` > `X`), a refresh of the JWKS is performed (if not already in progress) and the token validation of the request needs to wait synchronously until the JWKS has been succesfully refreshed.
- `refresh period`: When a JWKS is needed for validation whose cache entry is within the refresh period (`time until expiration` < `Y`), the cached JWKS will be used for validation (unless it has expired completely) and the JWKS will be refreshed asynchronously in the background.
**Conditions:**
The cacheSize value has to be >=1000
The expirationTime is measured in minutes and has to be a number >= 10
Only **one HTTP request at a time** will be performed to refresh the JWKS.
**Currently the default values are:**
In effect, productive systems with regular incoming requests should not experience delays from refreshing the JWKS (apart from the first request). Delays will only happen when the JWKS could not be refreshed during the refresh period, e.g. due to a prolonged outage of the JWKS endpoint or when no requests were received during the refresh period that would trigger a refresh.
##### Default cache configuration
```json
{
"cacheSize": 1000,
"expirationTime": 10
"expirationTime": 1800000, // 1800000ms = 30min
"refreshPeriod": 900000, // 900000ms = 15min
}
```
**Here are some codesnippets how to do this:**
##### Manual cache configuration
In rare situations you might need to change the cache configuration.
The expiration time is important to support key rotation scenarios and should not be too high. Otherwise, the security of the application is impacted.
:exclamation: **Normally you don't need to overwrite the default values!**
To overwrite cache parameters you need to specify them as key/value pairs under `<serviceCredentialJSON>.jwksCache`.
Please note that the cache parameters are configured in `ms` (milliseconds).
***Example code:***
```js
//just add the keyCache object into the config object and pass it to the constructor functions
// load service config
let config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
var config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
config.keyCache = {
cacheSize: 5000,
expirationTime: 10
// add a jwksCache object to the config
config.jwksCache = {
expirationTime: 3600000
refreshPeriod: 1800000,
};
//if you only want to overwrite one value you can also:
var config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
config.keyCache = {
cacheSize: 10000
// you can also overwrite only one value:
config.jwksCache = {
refreshPeriod: 1200000,
};
//then pass the config object to createSecurityConfig
// pass the config object to createSecurityConfig ...
xssec.createSecurityContext(access_token, config, function(error, securityContext, tokenInfo) {

@@ -217,7 +198,3 @@ ...

//if you use passport:
var config = xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa;
config.keyCache = {
cacheSize: 10000
};
// ... or if you use passport:
passport.use(new JWTStrategy(config));

@@ -224,0 +201,0 @@ ...

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc