SonarSource/sonarqube · error · IllegalArgumentException
Tokens expiring after %s are not allowed. Please use an expi
Error message
Tokens expiring after %s are not allowed. Please use an expiration date.
What it means
GenerateActionValidation throws this IllegalArgumentException when the server-wide max token lifetime policy is set in 'no expiration allowed' mode (validateMaxExpirationDate): any token generation request is rejected outright, with the message naming the effective cutoff date. The policy forbids non-expiring tokens, so callers must supply an expiration date within the allowed window.
Source
Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateActionValidation.java:96
void validateExpirationDate(@Nullable LocalDate expirationDate) {
MaxTokenLifetimeOption maxTokenLifetime = getMaxTokenLifetimeOption();
if (expirationDate != null) {
validateMinExpirationDate(expirationDate);
validateMaxExpirationDate(maxTokenLifetime, expirationDate);
} else {
validateMaxExpirationDate(maxTokenLifetime);
}
}
static void validateMaxExpirationDate(MaxTokenLifetimeOption maxTokenLifetime, LocalDate expirationDate) {
maxTokenLifetime.getDays()
.ifPresent(days -> compareExpirationDateToMaxAllowedLifetime(expirationDate, LocalDate.now(ZoneOffset.UTC).plusDays(days)));
}
static void validateMaxExpirationDate(MaxTokenLifetimeOption maxTokenLifetime) {
maxTokenLifetime.getDays()
.ifPresent(days -> {
throw new IllegalArgumentException(
String.format("Tokens expiring after %s are not allowed. Please use an expiration date.",
LocalDate.now(ZoneOffset.UTC).plusDays(days).format(DateTimeFormatter.ISO_DATE)));
});
}
static void compareExpirationDateToMaxAllowedLifetime(LocalDate expirationDate, LocalDate maxExpirationDate) {
if (expirationDate.isAfter(maxExpirationDate)) {
throw new IllegalArgumentException(
String.format("Tokens expiring after %s are not allowed. Please use a valid expiration date.",
maxExpirationDate.format(DateTimeFormatter.ISO_DATE)));
}
}
static void validateMinExpirationDate(LocalDate localDate) {
if (localDate.isBefore(LocalDate.now(ZoneOffset.UTC).plusDays(1))) {
throw new IllegalArgumentException(
String.format("The minimum value for parameter %s is %s.", PARAM_EXPIRATION_DATE, LocalDate.now(ZoneOffset.UTC).plusDays(1).format(DateTimeFormatter.ISO_DATE)));
}View on GitHub (pinned to 184c821202)
Solutions
- Always pass an expiration_date on or before the stated cutoff (date printed in the message).
- Shorten token lifetime in automation: generate tokens for the minimum needed duration and rotate them.
- If generation must be unexpiring for service accounts, request a policy exception and adjust sonar.auth.token.max-allowed-lifetime (server admin action).
- Migrate CI to alternative credentials (e.g. short-lived tokens refreshed by pipeline) that comply with the policy.
Example fix
// before: no expiration under a max-lifetime policy curl -su "$TOKEN:" -X POST 'https://sonar/api/user_tokens/generate?name=ci' // after: bounded expiration EXP=$(date -u -d '+30 days' +%F) curl -su "$TOKEN:" -X POST "https://sonar/api/user_tokens/generate?name=ci&expiration_date=$EXP"
Defensive patterns
Strategy: validation
Validate before calling
function withinMaxLifetime(expiryIso, maxDays) {
const cutoff = new Date(Date.now() + maxDays * 86400000).toISOString().slice(0, 10);
return expiryIso != null && expiryIso <= cutoff;
}
if (!expirationDate) throw new Error('policy requires an expiration_date; non-expiring tokens are not allowed'); Type guard
function hasAllowedExpiration(e) {
return typeof e === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(e);
} Try / catch
try {
await generateToken(name, exp);
} catch (e) {
if (e.status === 400 && /Tokens expiring after .* are not allowed/.test(e.message)) {
const cutoff = e.message.match(/after (\d{4}-\d{2}-\d{2})/)?.[1];
return generateToken(name, cutoff); // clamp to policy boundary
}
throw e;
} Prevention
- Always send expiration_date under a max-lifetime policy
- Read the policy value (sonar.auth.token.max-allowed-lifetime) and clamp dates in scripts
- Rotate short-lived tokens instead of minting long-lived ones
- Re-test token automation after security policy changes
When it happens
Trigger: POST api/user_tokens/generate (including with an expiration_date) when sonar.auth.token.max-allowed-lifetime is configured so that getDays() is present and validateMaxExpirationDate is reached — i.e. the server policy prohibits the requested lifetime entirely.
Common situations: Enterprise security policy enabled a hard maximum token lifetime; automation still generating long-lived or no-expiration tokens; scripts written before the lifetime policy feature was introduced; server hardened after an audit.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Tokens expiring after %s are not allowed. Please use a valid
- User with login '%s' has not been found
- parameter %s requires Administer System permission.
- Unknown type '%s'
- User '%s' doesn't exist
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/bcb8033224470f6f.
Report an issue: GitHub.