Skip to content

Commit 3a2cd15

Browse files
Merge pull request #105 from auth0/resource-server-settings
Resource server settings
1 parent 9c265a1 commit 3a2cd15

File tree

9 files changed

+603
-3
lines changed

9 files changed

+603
-3
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
{
3+
"id": "xxxxxxxxxxxx",
4+
"name": "test wrong alg",
5+
"identifier": "test1",
6+
"allow_offline_access": false,
7+
"skip_consent_for_verifiable_first_party_clients": true,
8+
"subject_type_authorization": {
9+
"user": {
10+
"policy": "require_client_grant"
11+
},
12+
"client": {
13+
"policy": "require_client_grant"
14+
}
15+
},
16+
"token_lifetime": 86400,
17+
"token_lifetime_for_web": 7200,
18+
"signing_alg": "HS256",
19+
"signing_secret": "xxxxxxxxxxxxxxxxxx",
20+
"token_dialect": "access_token"
21+
}
22+
*/
23+
const _ = require("lodash");
24+
const executeCheck = require("../executeCheck");
25+
const CONSTANTS = require("../constants");
26+
27+
function checkAPISigningAlgorithm(options) {
28+
return executeCheck("checkAPISigningAlgorithm", (callback) => {
29+
const { resourceServers } = options;
30+
const reports = [];
31+
32+
if (_.isEmpty(resourceServers)) {
33+
return callback(reports);
34+
}
35+
36+
resourceServers.forEach((api) => {
37+
// Skip the Auth0 Management API (system API)
38+
if (api.is_system) {
39+
return;
40+
}
41+
42+
const report = [];
43+
const apiDisplayName = api.identifier
44+
? `${api.name} (${api.identifier})`
45+
: api.name;
46+
47+
if (api.signing_alg === "HS256") {
48+
report.push({
49+
name: apiDisplayName,
50+
api_name: api.name,
51+
field: "using_symmetric_alg",
52+
status: CONSTANTS.FAIL,
53+
value: api.signing_alg,
54+
identifier: api.identifier,
55+
});
56+
} else {
57+
report.push({
58+
name: apiDisplayName,
59+
api_name: api.name,
60+
field: "using_asymmetric_alg",
61+
status: CONSTANTS.SUCCESS,
62+
value: api.signing_alg || "RS256",
63+
identifier: api.identifier,
64+
});
65+
}
66+
67+
if (report.length > 0) {
68+
reports.push({ name: apiDisplayName, report: report });
69+
}
70+
});
71+
72+
return callback(reports);
73+
});
74+
}
75+
76+
module.exports = checkAPISigningAlgorithm;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
{
3+
"id": "xxxxxxxxx",
4+
"name": "Test long token",
5+
"identifier": "test2",
6+
"allow_offline_access": true,
7+
"skip_consent_for_verifiable_first_party_clients": true,
8+
"subject_type_authorization": {
9+
"client": {
10+
"policy": "require_client_grant"
11+
},
12+
"user": {
13+
"policy": "allow_all"
14+
}
15+
},
16+
"token_lifetime": 864000,
17+
"token_lifetime_for_web": 7200,
18+
"signing_alg": "RS256",
19+
"signing_secret": "xxxxxxxxxxxxxx",
20+
"enforce_policies": false,
21+
"token_dialect": "access_token"
22+
}
23+
*/
24+
const _ = require("lodash");
25+
const executeCheck = require("../executeCheck");
26+
const CONSTANTS = require("../constants");
27+
28+
// Token lifetime thresholds (in seconds)
29+
const TOKEN_LIFETIME_DEFAULT = 86400; // 24 hours - Auth0 default
30+
const TOKEN_LIFETIME_WARNING = 604800; // 7 days
31+
32+
function checkAPITokenLifetime(options) {
33+
return executeCheck("checkAPITokenLifetime", (callback) => {
34+
const { resourceServers } = options;
35+
const reports = [];
36+
37+
if (_.isEmpty(resourceServers)) {
38+
return callback(reports);
39+
}
40+
41+
resourceServers.forEach((api) => {
42+
// Skip the Auth0 Management API (system API)
43+
if (api.is_system) {
44+
return;
45+
}
46+
47+
const report = [];
48+
const apiDisplayName = api.identifier
49+
? `${api.name} (${api.identifier})`
50+
: api.name;
51+
52+
const tokenLifetime = api.token_lifetime || TOKEN_LIFETIME_DEFAULT;
53+
54+
if (tokenLifetime >= TOKEN_LIFETIME_WARNING) {
55+
report.push({
56+
name: apiDisplayName,
57+
api_name: api.name,
58+
field: "token_lifetime_too_long",
59+
status: CONSTANTS.FAIL,
60+
value: formatDuration(tokenLifetime),
61+
identifier: api.identifier,
62+
});
63+
} else if (tokenLifetime > TOKEN_LIFETIME_DEFAULT) {
64+
report.push({
65+
name: apiDisplayName,
66+
api_name: api.name,
67+
field: "token_lifetime_extended",
68+
status: CONSTANTS.WARN,
69+
value: formatDuration(tokenLifetime),
70+
identifier: api.identifier,
71+
});
72+
} else {
73+
report.push({
74+
name: apiDisplayName,
75+
api_name: api.name,
76+
field: "token_lifetime_appropriate",
77+
status: CONSTANTS.SUCCESS,
78+
value: formatDuration(tokenLifetime),
79+
identifier: api.identifier,
80+
});
81+
}
82+
83+
if (report.length > 0) {
84+
reports.push({ name: apiDisplayName, report: report });
85+
}
86+
});
87+
88+
return callback(reports);
89+
});
90+
}
91+
92+
/**
93+
* Format duration in seconds to human-readable string
94+
* @param {number} seconds - Duration in seconds
95+
* @returns {string} - Formatted duration (e.g., "24 hours", "7 days")
96+
*/
97+
function formatDuration(seconds) {
98+
if (seconds >= 86400) {
99+
const days = Math.floor(seconds / 86400);
100+
return `${days} day${days > 1 ? "s" : ""} (${seconds} seconds)`;
101+
} else if (seconds >= 3600) {
102+
const hours = Math.floor(seconds / 3600);
103+
return `${hours} hour${hours > 1 ? "s" : ""} (${seconds} seconds)`;
104+
} else if (seconds >= 60) {
105+
const minutes = Math.floor(seconds / 60);
106+
return `${minutes} minute${minutes > 1 ? "s" : ""} (${seconds} seconds)`;
107+
}
108+
return `${seconds} seconds`;
109+
}
110+
111+
module.exports = checkAPITokenLifetime;

analyzer/report.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const {
2424
getLogs,
2525
getNetworkACL,
2626
getEventStreams,
27+
getResourceServers,
2728
} = require("./tools/auth0");
2829

2930
const logger = require("./lib/logger");
@@ -166,7 +167,12 @@ async function generateReport(locale, tenantConfig, config) {
166167
config.auth0MgmtToken
167168
);
168169

170+
tenantConfig.resourceServers = await getResourceServers(
171+
config.auth0Domain,
172+
config.auth0MgmtToken
173+
);
169174
}
175+
170176
const statusOrder = ["green", "amber", "red"];
171177
let fullReport =
172178
(await runProductionChecks(tenantConfig, config.selectedValidators)) ||
@@ -407,6 +413,24 @@ async function generateReport(locale, tenantConfig, config) {
407413
cd.message = i18n.__(`${report.name}.${cd.field}`, cd.value);
408414
});
409415
break;
416+
case "checkAPISigningAlgorithm":
417+
case "checkAPITokenLifetime":
418+
report.advisory = i18n.__(`${report.name}.advisory`);
419+
grouped = _.groupBy(report.details, "name");
420+
res = tranformReport(grouped);
421+
res.forEach((api) => {
422+
api.values.forEach((detail) => {
423+
detail.report.forEach((c) => {
424+
c.name = api.name;
425+
c.message = i18n.__(
426+
`${report.name}.${c.field}`,
427+
c.api_name,
428+
c.value
429+
);
430+
});
431+
});
432+
});
433+
break;
410434
default:
411435
report.details.forEach((cd) => {
412436
cd.message = i18n.__(`${report.name}.${cd.field}`, cd.value);

analyzer/tools/auth0.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,20 @@ async function getEventStreams(domain, accessToken) {
463463
return [error.response.data];
464464
}
465465
}
466+
467+
async function getResourceServers(domain, accessToken) {
468+
const url = `https://${domain}/api/v2/resource-servers`;
469+
const headers = { Authorization: `Bearer ${accessToken}` };
470+
logger.log("info", `Getting resource servers (APIs)`);
471+
472+
try {
473+
const response = await axios.get(url, { headers });
474+
return response.data;
475+
} catch (error) {
476+
logger.log("error", `Failed to get resource servers ${error.message}`);
477+
return [];
478+
}
479+
}
466480
module.exports = {
467481
getAccessToken,
468482
getCustomDomains,
@@ -486,4 +500,5 @@ module.exports = {
486500
getLogs,
487501
getNetworkACL,
488502
getEventStreams,
503+
getResourceServers,
489504
};

locales/en.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@
155155
"title": "Event Streams (Early Access Capability)",
156156
"required_scope": "read:event_streamss",
157157
"items": []
158+
},
159+
{
160+
"title": "Resource Servers (APIs)",
161+
"required_scope": "read:resource_servers",
162+
"items": [
163+
"Signing Algorithm",
164+
"Token Lifetime"
165+
]
158166
}
159167
],
160168
"validator_summary": "Checked <b>%s</b> validators against tenant <b>%s</b> in total.",
@@ -1478,6 +1486,59 @@
14781486
"https://auth0.com/docs/customize/events/event-testing-observability-and-failure-recovery"
14791487
]
14801488
},
1489+
"checkAPISigningAlgorithm": {
1490+
"title": "Resource Servers (APIs) - Signing Algorithm",
1491+
"category": "Resource Servers (APIs)",
1492+
"description": "Validates that APIs use RS256 signing algorithm instead of HS256.",
1493+
"docsPath": [
1494+
"https://auth0.com/docs/get-started/apis/api-settings"
1495+
],
1496+
"severity": "Moderate",
1497+
"status": "yellow",
1498+
"severity_message": "%s API(s) using insecure signing algorithm",
1499+
"advisory": {
1500+
"issue": "Insecure Token Signing Algorithm",
1501+
"description": {
1502+
"what_it_is": "The signing algorithm determines how access tokens are cryptographically signed.",
1503+
"why_its_risky": [
1504+
"HS256 requires sharing the signing secret with your API, increasing the risk of secret exposure. Anyone with the HS256 secret can forge valid tokens."
1505+
]
1506+
},
1507+
"how_to_fix": [
1508+
"Use RS256 (asymmetric) signing algorithm for new APIs. It is recommended because the token will be signed with your tenant private key."
1509+
]
1510+
},
1511+
"using_symmetric_alg": "API \"%s\" is using HS256 signing algorithm. RS256 is recommended for better security.",
1512+
"using_asymmetric_alg": "API \"%s\" is using %s signing algorithm."
1513+
},
1514+
"checkAPITokenLifetime": {
1515+
"title": "Resource Servers (APIs) - Token Lifetime",
1516+
"category": "Resource Servers (APIs)",
1517+
"description": "Validates that API access token lifetimes are appropriately configured. Shorter lifetimes reduce the window of opportunity if tokens are compromised. Auth0 recommends that sensitive APIs use shorter token lifetimes.",
1518+
"docsPath": [
1519+
"https://auth0.com/docs/get-started/apis/api-settings"
1520+
],
1521+
"severity": "Info",
1522+
"status": "blue",
1523+
"severity_message": "%s API(s) have extended token lifetimes",
1524+
"advisory": {
1525+
"issue": "Extended Access Token Lifetime",
1526+
"description": {
1527+
"what_it_is": "Token lifetime determines how long an access token remains valid. Auth0's default is 24 hours (86400 seconds), with a maximum of 30 days.",
1528+
"why_its_risky": [
1529+
"Long-lived tokens increase the window of opportunity for attackers if tokens are compromised.",
1530+
"Auth0 recommends that sensitive APIs use shorter token lifetimes (e.g., a banking API should expire more quickly than a to-do API)."
1531+
]
1532+
},
1533+
"how_to_fix": [
1534+
"Review token lifetime settings for each API based on sensitivity.",
1535+
"Use shorter lifetimes (e.g., 1 hour) for sensitive APIs."
1536+
]
1537+
},
1538+
"token_lifetime_too_long": "API \"%s\" has an access token lifetime of %s. This exceeds the recommended maximum of 7 days. Consider reducing the lifetime for sensitive APIs.",
1539+
"token_lifetime_extended": "API \"%s\" has an access token lifetime of %s. This exceeds the default of 24 hours. Review if this is appropriate for your security requirements.",
1540+
"token_lifetime_appropriate": "API \"%s\" has an access token lifetime of %s."
1541+
},
14811542
"checkDPoP": {
14821543
"title": "Applications - Token Sender-Constraining",
14831544
"category": "Applications",

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@auth0/auth0-checkmate",
3-
"version": "1.6.18",
3+
"version": "1.7.0",
44
"description": "A command line tool for checking configuration of your Auth0 tenant",
55
"main": "analyzer/report.js",
66
"scripts": {

0 commit comments

Comments
 (0)