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

  1. Always pass an expiration_date on or before the stated cutoff (date printed in the message).
  2. Shorten token lifetime in automation: generate tokens for the minimum needed duration and rotate them.
  3. If generation must be unexpiring for service accounts, request a policy exception and adjust sonar.auth.token.max-allowed-lifetime (server admin action).
  4. 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

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


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/bcb8033224470f6f. Report an issue: GitHub.